NCChatController.m 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844
  1. /**
  2. * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
  3. * SPDX-License-Identifier: GPL-3.0-or-later
  4. */
  5. #import "NCChatController.h"
  6. #import "NCAPIController.h"
  7. #import "NCChatBlock.h"
  8. #import "NCDatabaseManager.h"
  9. #import "NCIntentController.h"
  10. #import "NCRoomsManager.h"
  11. #import "NextcloudTalk-Swift.h"
  12. NSString * const NCChatControllerDidReceiveInitialChatHistoryNotification = @"NCChatControllerDidReceiveInitialChatHistoryNotification";
  13. NSString * const NCChatControllerDidReceiveInitialChatHistoryOfflineNotification = @"NCChatControllerDidReceiveInitialChatHistoryOfflineNotification";
  14. NSString * const NCChatControllerDidReceiveChatHistoryNotification = @"NCChatControllerDidReceiveChatHistoryNotification";
  15. NSString * const NCChatControllerDidReceiveChatMessagesNotification = @"NCChatControllerDidReceiveChatMessagesNotification";
  16. NSString * const NCChatControllerDidSendChatMessageNotification = @"NCChatControllerDidSendChatMessageNotification";
  17. NSString * const NCChatControllerDidReceiveChatBlockedNotification = @"NCChatControllerDidReceiveChatBlockedNotification";
  18. NSString * const NCChatControllerDidReceiveNewerCommonReadMessageNotification = @"NCChatControllerDidReceiveNewerCommonReadMessageNotification";
  19. NSString * const NCChatControllerDidReceiveUpdateMessageNotification = @"NCChatControllerDidReceiveUpdateMessageNotification";
  20. NSString * const NCChatControllerDidReceiveHistoryClearedNotification = @"NCChatControllerDidReceiveHistoryClearedNotification";
  21. NSString * const NCChatControllerDidReceiveCallStartedMessageNotification = @"NCChatControllerDidReceiveCallStartedMessageNotification";
  22. NSString * const NCChatControllerDidReceiveCallEndedMessageNotification = @"NCChatControllerDidReceiveCallEndedMessageNotification";
  23. NSString * const NCChatControllerDidReceiveMessagesInBackgroundNotification = @"NCChatControllerDidReceiveMessagesInBackgroundNotification";
  24. @interface NCChatController ()
  25. @property (nonatomic, assign) BOOL stopChatMessagesPoll;
  26. @property (nonatomic, strong) TalkAccount *account;
  27. @property (nonatomic, strong) NSURLSessionTask *getHistoryTask;
  28. @property (nonatomic, strong) NSURLSessionTask *pullMessagesTask;
  29. @end
  30. @implementation NCChatController
  31. - (instancetype)initForRoom:(NCRoom *)room
  32. {
  33. self = [super init];
  34. if (self) {
  35. _room = room;
  36. _account = [[NCDatabaseManager sharedInstance] talkAccountForAccountId:_room.accountId];
  37. [[AllocationTracker shared] addAllocation:@"NCChatController"];
  38. }
  39. return self;
  40. }
  41. - (void)dealloc
  42. {
  43. [[AllocationTracker shared] removeAllocation:@"NCChatController"];
  44. }
  45. #pragma mark - Database
  46. - (NSArray *)chatBlocksForRoom
  47. {
  48. RLMResults *managedBlocks = [NCChatBlock objectsWhere:@"internalId = %@", _room.internalId];
  49. RLMResults *managedSortedBlocks = [managedBlocks sortedResultsUsingKeyPath:@"newestMessageId" ascending:YES];
  50. // Create an unmanaged copy of the blocks
  51. NSMutableArray *sortedBlocks = [NSMutableArray new];
  52. for (NCChatBlock *managedBlock in managedSortedBlocks) {
  53. NCChatBlock *sortedBlock = [[NCChatBlock alloc] initWithValue:managedBlock];
  54. [sortedBlocks addObject:sortedBlock];
  55. }
  56. return sortedBlocks;
  57. }
  58. - (NSArray *)getBatchOfMessagesInBlock:(NCChatBlock *)chatBlock fromMessageId:(NSInteger)messageId included:(BOOL)included
  59. {
  60. NSInteger fromMessageId = messageId > 0 ? messageId : chatBlock.newestMessageId;
  61. NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@ AND token = %@ AND messageId >= %ld AND messageId < %ld", _account.accountId, _room.token, (long)chatBlock.oldestMessageId, (long)fromMessageId];
  62. if (included) {
  63. query = [NSPredicate predicateWithFormat:@"accountId = %@ AND token = %@ AND messageId >= %ld AND messageId <= %ld", _account.accountId, _room.token, (long)chatBlock.oldestMessageId, (long)fromMessageId];
  64. }
  65. RLMResults *managedMessages = [NCChatMessage objectsWithPredicate:query];
  66. RLMResults *managedSortedMessages = [managedMessages sortedResultsUsingKeyPath:@"messageId" ascending:YES];
  67. // Create an unmanaged copy of the messages
  68. NSMutableArray *sortedMessages = [NSMutableArray new];
  69. NSInteger startingIndex = managedSortedMessages.count - kReceivedChatMessagesLimit;
  70. startingIndex = (startingIndex < 0) ? 0 : startingIndex;
  71. for (NSInteger i = startingIndex; i < managedSortedMessages.count; i++) {
  72. NCChatMessage *sortedMessage = [[NCChatMessage alloc] initWithValue:managedSortedMessages[i]];
  73. [sortedMessages addObject:sortedMessage];
  74. }
  75. return sortedMessages;
  76. }
  77. - (NSArray *)getNewStoredMessagesInBlock:(NCChatBlock *)chatBlock sinceMessageId:(NSInteger)messageId
  78. {
  79. NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@ AND token = %@ AND messageId > %ld AND messageId <= %ld", _account.accountId, _room.token, (long)messageId, (long)chatBlock.newestMessageId];
  80. RLMResults *managedMessages = [NCChatMessage objectsWithPredicate:query];
  81. RLMResults *managedSortedMessages = [managedMessages sortedResultsUsingKeyPath:@"messageId" ascending:YES];
  82. // Create an unmanaged copy of the messages
  83. NSMutableArray *sortedMessages = [NSMutableArray new];
  84. for (NCChatMessage *managedMessage in managedSortedMessages) {
  85. NCChatMessage *sortedMessage = [[NCChatMessage alloc] initWithValue:managedMessage];
  86. [sortedMessages addObject:sortedMessage];
  87. }
  88. return sortedMessages;
  89. }
  90. - (void)storeMessages:(NSArray *)messages withRealm:(RLMRealm *)realm {
  91. // Add or update messages
  92. for (NSDictionary *messageDict in messages) {
  93. // messageWithDictionary takes care of setting a potential available parentId
  94. NCChatMessage *message = [NCChatMessage messageWithDictionary:messageDict andAccountId:_account.accountId];
  95. if (message.referenceId && ![message.referenceId isEqualToString:@""]) {
  96. NCChatMessage *managedTemporaryMessage = [NCChatMessage objectsWhere:@"referenceId = %@ AND isTemporary = true", message.referenceId].firstObject;
  97. if (managedTemporaryMessage) {
  98. [realm deleteObject:managedTemporaryMessage];
  99. }
  100. }
  101. NCChatMessage *managedMessage = [NCChatMessage objectsWhere:@"internalId = %@", message.internalId].firstObject;
  102. if (managedMessage) {
  103. [NCChatMessage updateChatMessage:managedMessage withChatMessage:message isRoomLastMessage:NO];
  104. } else if (message) {
  105. [realm addObject:message];
  106. }
  107. NCChatMessage *parent = [NCChatMessage messageWithDictionary:[messageDict objectForKey:@"parent"] andAccountId:_account.accountId];
  108. NCChatMessage *managedParentMessage = [NCChatMessage objectsWhere:@"internalId = %@", parent.internalId].firstObject;
  109. if (managedParentMessage) {
  110. // updateChatMessage takes care of not setting a parentId to nil if there was one before
  111. [NCChatMessage updateChatMessage:managedParentMessage withChatMessage:parent isRoomLastMessage:NO];
  112. } else if (parent) {
  113. [realm addObject:parent];
  114. }
  115. }
  116. }
  117. - (void)storeMessages:(NSArray *)messages
  118. {
  119. RLMRealm *realm = [RLMRealm defaultRealm];
  120. [realm transactionWithBlock:^{
  121. [self storeMessages:messages withRealm:realm];
  122. }];
  123. }
  124. - (BOOL)hasOlderStoredMessagesThanMessageId:(NSInteger)messageId
  125. {
  126. NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@ AND token = %@ AND messageId < %ld", _account.accountId, _room.token, (long)messageId];
  127. return [NCChatMessage objectsWithPredicate:query].count > 0;
  128. }
  129. - (void)removeAllStoredMessagesAndChatBlocks
  130. {
  131. RLMRealm *realm = [RLMRealm defaultRealm];
  132. [realm transactionWithBlock:^{
  133. NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@ AND token = %@", _account.accountId, _room.token];
  134. [realm deleteObjects:[NCChatMessage objectsWithPredicate:query]];
  135. [realm deleteObjects:[NCChatBlock objectsWithPredicate:query]];
  136. }];
  137. }
  138. - (void)removeExpiredMessages
  139. {
  140. RLMRealm *realm = [RLMRealm defaultRealm];
  141. NSInteger currentTimestamp = [[NSDate date] timeIntervalSince1970];
  142. [realm transactionWithBlock:^{
  143. NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@ AND token = %@ AND expirationTimestamp > 0 AND expirationTimestamp <= %ld", _account.accountId, _room.token, currentTimestamp];
  144. [realm deleteObjects:[NCChatMessage objectsWithPredicate:query]];
  145. }];
  146. }
  147. - (void)updateLastChatBlockWithNewestKnown:(NSInteger)newestKnown
  148. {
  149. RLMRealm *realm = [RLMRealm defaultRealm];
  150. [realm transactionWithBlock:^{
  151. RLMResults *managedBlocks = [NCChatBlock objectsWhere:@"internalId = %@", _room.internalId];
  152. RLMResults *managedSortedBlocks = [managedBlocks sortedResultsUsingKeyPath:@"newestMessageId" ascending:YES];
  153. NCChatBlock *lastBlock = managedSortedBlocks.lastObject;
  154. if (newestKnown > 0) {
  155. lastBlock.newestMessageId = newestKnown;
  156. }
  157. }];
  158. }
  159. - (void)updateChatBlocksWithLastKnown:(NSInteger)lastKnown
  160. {
  161. if (lastKnown <= 0) {
  162. return;
  163. }
  164. RLMRealm *realm = [RLMRealm defaultRealm];
  165. [realm transactionWithBlock:^{
  166. RLMResults *managedBlocks = [NCChatBlock objectsWhere:@"internalId = %@", _room.internalId];
  167. RLMResults *managedSortedBlocks = [managedBlocks sortedResultsUsingKeyPath:@"newestMessageId" ascending:YES];
  168. NCChatBlock *lastBlock = managedSortedBlocks.lastObject;
  169. // There is more than one chat block stored
  170. if (managedSortedBlocks.count > 1) {
  171. for (NSInteger i = managedSortedBlocks.count - 2; i >= 0; i--) {
  172. NCChatBlock *block = managedSortedBlocks[i];
  173. // Merge blocks if the lastKnown message is inside the current block
  174. if (lastKnown >= block.oldestMessageId && lastKnown <= block.newestMessageId) {
  175. lastBlock.oldestMessageId = block.oldestMessageId;
  176. [realm deleteObject:block];
  177. break;
  178. // Update lastBlock if the lastKnown message is between the 2 blocks
  179. } else if (lastKnown > block.newestMessageId) {
  180. lastBlock.oldestMessageId = lastKnown;
  181. break;
  182. // The current block is completely included in the retrieved history
  183. // This could happen if we vary the message limit when fetching messages
  184. // Delete included block
  185. } else if (lastKnown < block.oldestMessageId) {
  186. [realm deleteObject:block];
  187. }
  188. }
  189. // There is just one chat block stored
  190. } else {
  191. lastBlock.oldestMessageId = lastKnown;
  192. }
  193. }];
  194. }
  195. - (void)updateChatBlocksWithReceivedMessages:(NSArray *)messages newestKnown:(NSInteger)newestKnown andLastKnown:(NSInteger)lastKnown
  196. {
  197. NSArray *sortedMessages = [self sortedMessagesFromMessageArray:messages];
  198. NCChatMessage *newestMessageReceived = sortedMessages.lastObject;
  199. NSInteger newestMessageKnown = newestKnown > 0 ? newestKnown : newestMessageReceived.messageId;
  200. RLMRealm *realm = [RLMRealm defaultRealm];
  201. [realm transactionWithBlock:^{
  202. RLMResults *managedBlocks = [NCChatBlock objectsWhere:@"internalId = %@", _room.internalId];
  203. RLMResults *managedSortedBlocks = [managedBlocks sortedResultsUsingKeyPath:@"newestMessageId" ascending:YES];
  204. // Create new chat block
  205. NCChatBlock *newBlock = [[NCChatBlock alloc] init];
  206. newBlock.internalId = _room.internalId;
  207. newBlock.accountId = _room.accountId;
  208. newBlock.token = _room.token;
  209. newBlock.oldestMessageId = lastKnown;
  210. newBlock.newestMessageId = newestMessageKnown;
  211. newBlock.hasHistory = YES;
  212. // There is at least one chat block stored
  213. if (managedSortedBlocks.count > 0) {
  214. for (NSInteger i = managedSortedBlocks.count - 1; i >= 0; i--) {
  215. NCChatBlock *block = managedSortedBlocks[i];
  216. // Merge blocks if the lastKnown message is inside the current block
  217. if (lastKnown >= block.oldestMessageId && lastKnown <= block.newestMessageId) {
  218. block.newestMessageId = newestMessageKnown;
  219. break;
  220. // Add new block if it didn't reach the previous block
  221. } else if (lastKnown > block.newestMessageId) {
  222. [realm addObject:newBlock];
  223. break;
  224. // The current block is completely included in the retrieved history
  225. // This could happen if we vary the message limit when fetching messages
  226. // Delete included block
  227. } else if (lastKnown < block.oldestMessageId) {
  228. [realm deleteObject:block];
  229. }
  230. }
  231. // No chat blocks stored yet, add new chat block
  232. } else {
  233. [realm addObject:newBlock];
  234. }
  235. }];
  236. }
  237. - (void)updateHistoryFlagInFirstBlock
  238. {
  239. RLMRealm *realm = [RLMRealm defaultRealm];
  240. [realm transactionWithBlock:^{
  241. RLMResults *managedBlocks = [NCChatBlock objectsWhere:@"internalId = %@", _room.internalId];
  242. RLMResults *managedSortedBlocks = [managedBlocks sortedResultsUsingKeyPath:@"newestMessageId" ascending:YES];
  243. NCChatBlock *firstChatBlock = managedSortedBlocks.firstObject;
  244. firstChatBlock.hasHistory = NO;
  245. }];
  246. }
  247. - (void)transactionForMessageWithReferenceId:(NSString *)referenceId withBlock:(void(^)(NCChatMessage *message))block
  248. {
  249. RLMRealm *realm = [RLMRealm defaultRealm];
  250. [realm transactionWithBlock:^{
  251. NCChatMessage *managedChatMessage = [NCChatMessage objectsWhere:@"referenceId = %@ AND isTemporary = true", referenceId].firstObject;
  252. block(managedChatMessage);
  253. }];
  254. }
  255. - (NSArray *)sortedMessagesFromMessageArray:(NSArray *)messages
  256. {
  257. NSMutableArray *sortedMessages = [[NSMutableArray alloc] initWithCapacity:messages.count];
  258. for (NSDictionary *messageDict in messages) {
  259. NCChatMessage *message = [NCChatMessage messageWithDictionary:messageDict];
  260. [sortedMessages addObject:message];
  261. }
  262. // Sort by messageId
  263. NSSortDescriptor *valueDescriptor = [[NSSortDescriptor alloc] initWithKey:@"messageId" ascending:YES];
  264. NSArray *descriptors = [NSArray arrayWithObject:valueDescriptor];
  265. [sortedMessages sortUsingDescriptors:descriptors];
  266. return sortedMessages;
  267. }
  268. #pragma mark - Chat
  269. - (NSArray<NCChatMessage *> * _Nonnull)getTemporaryMessages
  270. {
  271. NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@ AND token = %@ AND isTemporary = true", _account.accountId, _room.token];
  272. RLMResults *managedTemporaryMessages = [NCChatMessage objectsWithPredicate:query];
  273. RLMResults *managedSortedTemporaryMessages = [managedTemporaryMessages sortedResultsUsingKeyPath:@"timestamp" ascending:YES];
  274. // Mark temporary messages sent more than 12 hours ago as failed-to-send messages
  275. NSInteger twelveHoursAgoTimestamp = [[NSDate date] timeIntervalSince1970] - (60 * 60 * 12);
  276. for (NCChatMessage *temporaryMessage in managedTemporaryMessages) {
  277. if (temporaryMessage.timestamp < twelveHoursAgoTimestamp) {
  278. RLMRealm *realm = [RLMRealm defaultRealm];
  279. [realm transactionWithBlock:^{
  280. temporaryMessage.isOfflineMessage = NO;
  281. temporaryMessage.sendingFailed = YES;
  282. }];
  283. }
  284. }
  285. // Create an unmanaged copy of the messages
  286. NSMutableArray *sortedMessages = [NSMutableArray new];
  287. for (NCChatMessage *managedMessage in managedSortedTemporaryMessages) {
  288. NCChatMessage *sortedMessage = [[NCChatMessage alloc] initWithValue:managedMessage];
  289. [sortedMessages addObject:sortedMessage];
  290. }
  291. return sortedMessages;
  292. }
  293. - (void)updateHistoryInBackgroundWithCompletionBlock:(UpdateHistoryInBackgroundCompletionBlock)block
  294. {
  295. // If there's a pull task running right now, we should not interfere with that
  296. if (_pullMessagesTask && _pullMessagesTask.state == NSURLSessionTaskStateRunning) {
  297. if (block) {
  298. NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:nil];
  299. block(error);
  300. }
  301. return;
  302. }
  303. NCChatBlock *lastChatBlock = [self chatBlocksForRoom].lastObject;
  304. __block BOOL expired = NO;
  305. BGTaskHelper *bgTask = [BGTaskHelper startBackgroundTaskWithName:@"updateHistoryInBackgroundWithCompletionBlock" expirationHandler:^(BGTaskHelper *task) {
  306. [NCUtils log:@"ExpirationHandler called updateHistoryInBackgroundWithCompletionBlock"];
  307. expired = YES;
  308. // Make sure we actually end a running pullMessagesTask, because otherwise the completion handler might not be called in time
  309. [self->_pullMessagesTask cancel];
  310. }];
  311. _pullMessagesTask = [[NCAPIController sharedInstance] receiveChatMessagesOfRoom:_room.token fromLastMessageId:lastChatBlock.newestMessageId history:NO includeLastMessage:NO timeout:NO lastCommonReadMessage:_room.lastCommonReadMessage setReadMarker:NO markNotificationsAsRead:NO forAccount:_account withCompletionBlock:^(NSArray *messages, NSInteger lastKnownMessage, NSInteger lastCommonReadMessage, NSError *error, NSInteger statusCode) {
  312. if (expired) {
  313. if (block) {
  314. block(error);
  315. }
  316. [bgTask stopBackgroundTask];
  317. return;
  318. }
  319. if (error) {
  320. NSLog(@"Could not get background chat history. Error: %@", error.description);
  321. } else {
  322. // Update chat blocks
  323. [self updateLastChatBlockWithNewestKnown:lastKnownMessage];
  324. // Store new messages
  325. if (messages.count > 0) {
  326. [self storeMessages:messages];
  327. [self checkLastCommonReadMessage:lastCommonReadMessage];
  328. // In case we finish after the app already got active again, notify any potential view controller
  329. NSMutableDictionary *userInfo = [NSMutableDictionary new];
  330. [userInfo setObject:self->_room.token forKey:@"room"];
  331. [[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveMessagesInBackgroundNotification
  332. object:self
  333. userInfo:userInfo];
  334. }
  335. }
  336. if (block) {
  337. block(error);
  338. }
  339. [bgTask stopBackgroundTask];
  340. }];
  341. }
  342. - (void)checkForNewMessagesFromMessageId:(NSInteger)messageId
  343. {
  344. NCChatBlock *lastChatBlock = [self chatBlocksForRoom].lastObject;
  345. NSArray *storedMessages = [self getNewStoredMessagesInBlock:lastChatBlock sinceMessageId:messageId];
  346. NSMutableDictionary *userInfo = [NSMutableDictionary new];
  347. [userInfo setObject:self->_room.token forKey:@"room"];
  348. if (storedMessages.count > 0) {
  349. for (NCChatMessage *message in storedMessages) {
  350. // Notify if "call started" have been received
  351. if ([message.systemMessage isEqualToString:@"call_started"]) {
  352. [[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveCallStartedMessageNotification
  353. object:self
  354. userInfo:userInfo];
  355. }
  356. // Notify if "call eneded" have been received
  357. if ([message.systemMessage isEqualToString:@"call_ended"] ||
  358. [message.systemMessage isEqualToString:@"call_ended_everyone"] ||
  359. [message.systemMessage isEqualToString:@"call_missed"] ||
  360. [message.systemMessage isEqualToString:@"call_tried"]) {
  361. [[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveCallEndedMessageNotification
  362. object:self
  363. userInfo:userInfo];
  364. }
  365. // Notify if an "update messages" have been received
  366. if ([message isUpdateMessage]) {
  367. [userInfo setObject:message forKey:@"updateMessage"];
  368. [[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveUpdateMessageNotification
  369. object:self
  370. userInfo:userInfo];
  371. }
  372. // Notify if "history cleared" has been received
  373. if ([message.systemMessage isEqualToString:@"history_cleared"]) {
  374. [userInfo setObject:message forKey:@"historyCleared"];
  375. [[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveHistoryClearedNotification
  376. object:self
  377. userInfo:userInfo];
  378. return;
  379. }
  380. }
  381. [userInfo setObject:storedMessages forKey:@"messages"];
  382. [userInfo setObject:@(!_hasReceivedMessagesFromServer) forKey:@"firstNewMessagesAfterHistory"];
  383. [[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveChatMessagesNotification
  384. object:self
  385. userInfo:userInfo];
  386. // Messages are already sorted by messageId here
  387. NCChatMessage *lastMessage = [storedMessages lastObject];
  388. // Make sure we update the unread flags for the room (lastMessage can already be set, but there still might be unread flags)
  389. if (lastMessage.timestamp >= self->_room.lastActivity && !lastMessage.isUpdateMessage) {
  390. self->_room.lastActivity = lastMessage.timestamp;
  391. [[NCRoomsManager sharedInstance] updateLastMessage:lastMessage withNoUnreadMessages:YES forRoom:self->_room];
  392. }
  393. }
  394. }
  395. - (void)getInitialChatHistory
  396. {
  397. NSMutableDictionary *userInfo = [NSMutableDictionary new];
  398. [userInfo setObject:_room.token forKey:@"room"];
  399. // Clear expired messages
  400. [self removeExpiredMessages];
  401. NSInteger lastReadMessageId = 0;
  402. if ([[NCDatabaseManager sharedInstance] roomHasTalkCapability:kCapabilityChatReadMarker forRoom:self.room]) {
  403. lastReadMessageId = _room.lastReadMessage;
  404. }
  405. NCChatBlock *lastChatBlock = [self chatBlocksForRoom].lastObject;
  406. if (lastChatBlock.newestMessageId > 0 && lastChatBlock.newestMessageId >= lastReadMessageId) {
  407. NSArray *storedMessages = [self getBatchOfMessagesInBlock:lastChatBlock fromMessageId:lastChatBlock.newestMessageId included:YES];
  408. [userInfo setObject:storedMessages forKey:@"messages"];
  409. [[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveInitialChatHistoryNotification
  410. object:self
  411. userInfo:userInfo];
  412. // Messages are already sorted by messageId here
  413. NCChatMessage *lastMessage = [storedMessages lastObject];
  414. // Make sure we update the unread flags for the room (lastMessage can already be set, but there still might be unread flags)
  415. if (lastMessage.timestamp >= self->_room.lastActivity && !lastMessage.isUpdateMessage) {
  416. self->_room.lastActivity = lastMessage.timestamp;
  417. [[NCRoomsManager sharedInstance] updateLastMessage:lastMessage withNoUnreadMessages:YES forRoom:self->_room];
  418. }
  419. } else {
  420. _pullMessagesTask = [[NCAPIController sharedInstance] receiveChatMessagesOfRoom:_room.token fromLastMessageId:lastReadMessageId history:YES includeLastMessage:YES timeout:NO lastCommonReadMessage:_room.lastCommonReadMessage setReadMarker:YES markNotificationsAsRead:YES forAccount:_account withCompletionBlock:^(NSArray *messages, NSInteger lastKnownMessage, NSInteger lastCommonReadMessage, NSError *error, NSInteger statusCode) {
  421. if (self->_stopChatMessagesPoll) {
  422. return;
  423. }
  424. if (error) {
  425. if ([self isChatBeingBlocked:statusCode]) {
  426. [self notifyChatIsBlocked];
  427. return;
  428. }
  429. [userInfo setObject:error forKey:@"error"];
  430. NSLog(@"Could not get initial chat history. Error: %@", error.description);
  431. } else {
  432. // Update chat blocks
  433. [self updateChatBlocksWithReceivedMessages:messages newestKnown:lastReadMessageId andLastKnown:lastKnownMessage];
  434. // Store new messages
  435. if (messages.count > 0) {
  436. [self storeMessages:messages];
  437. NCChatBlock *lastChatBlock = [self chatBlocksForRoom].lastObject;
  438. NSArray *storedMessages = [self getBatchOfMessagesInBlock:lastChatBlock fromMessageId:lastReadMessageId included:YES];
  439. [userInfo setObject:storedMessages forKey:@"messages"];
  440. }
  441. }
  442. [[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveInitialChatHistoryNotification
  443. object:self
  444. userInfo:userInfo];
  445. [self checkLastCommonReadMessage:lastCommonReadMessage];
  446. }];
  447. }
  448. }
  449. - (void)getInitialChatHistoryForOfflineMode
  450. {
  451. NSMutableDictionary *userInfo = [NSMutableDictionary new];
  452. [userInfo setObject:_room.token forKey:@"room"];
  453. NCChatBlock *lastChatBlock = [self chatBlocksForRoom].lastObject;
  454. NSArray *storedMessages = [self getBatchOfMessagesInBlock:lastChatBlock fromMessageId:lastChatBlock.newestMessageId included:YES];
  455. [userInfo setObject:storedMessages forKey:@"messages"];
  456. [[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveInitialChatHistoryOfflineNotification
  457. object:self
  458. userInfo:userInfo];
  459. }
  460. - (void)getHistoryBatchFromMessagesId:(NSInteger)messageId
  461. {
  462. NSMutableDictionary *userInfo = [NSMutableDictionary new];
  463. [userInfo setObject:_room.token forKey:@"room"];
  464. NCChatBlock *lastChatBlock = [self chatBlocksForRoom].lastObject;
  465. if (lastChatBlock && lastChatBlock.oldestMessageId < messageId) {
  466. NSArray *storedMessages = [self getBatchOfMessagesInBlock:lastChatBlock fromMessageId:messageId included:NO];
  467. [userInfo setObject:storedMessages forKey:@"messages"];
  468. [[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveChatHistoryNotification
  469. object:self
  470. userInfo:userInfo];
  471. } else {
  472. _getHistoryTask = [[NCAPIController sharedInstance] receiveChatMessagesOfRoom:_room.token fromLastMessageId:messageId history:YES includeLastMessage:NO timeout:NO lastCommonReadMessage:_room.lastCommonReadMessage setReadMarker:YES markNotificationsAsRead:YES forAccount:_account withCompletionBlock:^(NSArray *messages, NSInteger lastKnownMessage, NSInteger lastCommonReadMessage, NSError *error, NSInteger statusCode) {
  473. if (statusCode == 304) {
  474. [self updateHistoryFlagInFirstBlock];
  475. }
  476. if (error) {
  477. if ([self isChatBeingBlocked:statusCode]) {
  478. [self notifyChatIsBlocked];
  479. return;
  480. }
  481. [userInfo setObject:error forKey:@"error"];
  482. if (statusCode != 304) {
  483. NSLog(@"Could not get chat history. Error: %@", error.description);
  484. }
  485. } else {
  486. // Update chat blocks
  487. [self updateChatBlocksWithLastKnown:lastKnownMessage];
  488. // Store new messages
  489. if (messages.count > 0) {
  490. [self storeMessages:messages];
  491. NCChatBlock *lastChatBlock = [self chatBlocksForRoom].lastObject;
  492. NSArray *historyBatch = [self getBatchOfMessagesInBlock:lastChatBlock fromMessageId:messageId included:NO];
  493. [userInfo setObject:historyBatch forKey:@"messages"];
  494. }
  495. }
  496. [[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveChatHistoryNotification
  497. object:self
  498. userInfo:userInfo];
  499. }];
  500. }
  501. }
  502. - (void)getHistoryBatchOfflineFromMessagesId:(NSInteger)messageId
  503. {
  504. NSMutableDictionary *userInfo = [NSMutableDictionary new];
  505. [userInfo setObject:_room.token forKey:@"room"];
  506. NSArray *chatBlocks = [self chatBlocksForRoom];
  507. NSMutableArray *historyBatch = [NSMutableArray new];
  508. if (chatBlocks.count > 0) {
  509. for (NSInteger i = chatBlocks.count - 1; i >= 0; i--) {
  510. NCChatBlock *currentBlock = chatBlocks[i];
  511. BOOL noMoreMessagesToRetrieveInBlock = NO;
  512. if (currentBlock.oldestMessageId < messageId) {
  513. NSArray *storedMessages = [self getBatchOfMessagesInBlock:currentBlock fromMessageId:messageId included:NO];
  514. historyBatch = [[NSMutableArray alloc] initWithArray:storedMessages];
  515. if (storedMessages.count > 0) {
  516. break;
  517. } else {
  518. // We use this flag in case the rest of the messages in current block
  519. // are system messages invisible for the user.
  520. noMoreMessagesToRetrieveInBlock = YES;
  521. }
  522. }
  523. if (i > 0 && (currentBlock.oldestMessageId == messageId || noMoreMessagesToRetrieveInBlock)) {
  524. NCChatBlock *previousBlock = chatBlocks[i - 1];
  525. NSArray *storedMessages = [self getBatchOfMessagesInBlock:previousBlock fromMessageId:previousBlock.newestMessageId included:YES];
  526. historyBatch = [[NSMutableArray alloc] initWithArray:storedMessages];
  527. [userInfo setObject:@(YES) forKey:@"shouldAddBlockSeparator"];
  528. break;
  529. }
  530. }
  531. }
  532. if (historyBatch.count == 0) {
  533. [userInfo setObject:@(YES) forKey:@"noMoreStoredHistory"];
  534. }
  535. [userInfo setObject:historyBatch forKey:@"messages"];
  536. [[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveChatHistoryNotification
  537. object:self
  538. userInfo:userInfo];
  539. }
  540. - (void)stopReceivingChatHistory
  541. {
  542. [_getHistoryTask cancel];
  543. }
  544. - (void)startReceivingChatMessagesFromMessagesId:(NSInteger)messageId withTimeout:(BOOL)timeout
  545. {
  546. _stopChatMessagesPoll = NO;
  547. [_pullMessagesTask cancel];
  548. _pullMessagesTask = [[NCAPIController sharedInstance] receiveChatMessagesOfRoom:_room.token fromLastMessageId:messageId history:NO includeLastMessage:NO timeout:timeout lastCommonReadMessage:_room.lastCommonReadMessage setReadMarker:YES markNotificationsAsRead:YES forAccount:_account withCompletionBlock:^(NSArray *messages, NSInteger lastKnownMessage, NSInteger lastCommonReadMessage, NSError *error, NSInteger statusCode) {
  549. if (self->_stopChatMessagesPoll) {
  550. return;
  551. }
  552. if (error) {
  553. if ([self isChatBeingBlocked:statusCode]) {
  554. [self notifyChatIsBlocked];
  555. return;
  556. }
  557. if (statusCode != 304) {
  558. NSLog(@"Could not get new chat messages. Error: %@", error.description);
  559. }
  560. } else {
  561. // Update last chat block
  562. [self updateLastChatBlockWithNewestKnown:lastKnownMessage];
  563. // Store new messages
  564. if (messages.count > 0) {
  565. [self storeMessages:messages];
  566. [self checkForNewMessagesFromMessageId:messageId];
  567. for (NSDictionary *messageDict in messages) {
  568. NCChatMessage *message = [NCChatMessage messageWithDictionary:messageDict andAccountId:self->_account.accountId];
  569. // When we receive a "history_cleared" message, we don't continue here, as otherwise
  570. // we would request new message, but instead, we need to request the inital history again
  571. if ([message.systemMessage isEqualToString:@"history_cleared"]) {
  572. return;
  573. }
  574. }
  575. }
  576. }
  577. self->_hasReceivedMessagesFromServer = YES;
  578. [self checkLastCommonReadMessage:lastCommonReadMessage];
  579. if (error.code != -999) {
  580. NCChatBlock *lastChatBlock = [self chatBlocksForRoom].lastObject;
  581. [self startReceivingChatMessagesFromMessagesId:lastChatBlock.newestMessageId withTimeout:YES];
  582. }
  583. }];
  584. }
  585. - (void)startReceivingNewChatMessages
  586. {
  587. NCChatBlock *lastChatBlock = [self chatBlocksForRoom].lastObject;
  588. [self startReceivingChatMessagesFromMessagesId:lastChatBlock.newestMessageId withTimeout:NO];
  589. }
  590. - (void)stopReceivingNewChatMessages
  591. {
  592. _stopChatMessagesPoll = YES;
  593. [_pullMessagesTask cancel];
  594. }
  595. - (void)sendChatMessage:(NSString *)message replyTo:(NSInteger)replyTo referenceId:(NSString *)referenceId silently:(BOOL)silently
  596. {
  597. BGTaskHelper *bgTask = [BGTaskHelper startBackgroundTaskWithName:@"NCChatControllerSendMessage" expirationHandler:^(BGTaskHelper *task) {
  598. [NCUtils log:@"ExpirationHandler called - sendChatMessage"];
  599. }];
  600. NSMutableDictionary *userInfo = [NSMutableDictionary new];
  601. [userInfo setObject:message forKey:@"message"];
  602. __block NSInteger retryCount;
  603. if (referenceId) {
  604. // Reset offline message flag before retrying to send to prevent race conditions and
  605. // possible ending up with multiple identical messages sent
  606. [self transactionForMessageWithReferenceId:referenceId withBlock:^(NCChatMessage *message) {
  607. message.isOfflineMessage = NO;
  608. retryCount = message.offlineMessageRetryCount;
  609. }];
  610. }
  611. [[NCAPIController sharedInstance] sendChatMessage:message toRoom:_room.token displayName:nil replyTo:replyTo referenceId:referenceId silently:silently forAccount:_account withCompletionBlock:^(NSError *error) {
  612. if (referenceId) {
  613. [userInfo setObject:referenceId forKey:@"referenceId"];
  614. }
  615. if (error) {
  616. [userInfo setObject:error forKey:@"error"];
  617. if (referenceId) {
  618. if (retryCount >= 5) {
  619. // After 5 retries, we assume sending is not possible
  620. [self transactionForMessageWithReferenceId:referenceId withBlock:^(NCChatMessage *message) {
  621. message.sendingFailed = YES;
  622. message.isOfflineMessage = NO;
  623. }];
  624. } else {
  625. [self transactionForMessageWithReferenceId:referenceId withBlock:^(NCChatMessage *message) {
  626. message.sendingFailed = NO;
  627. message.isOfflineMessage = YES;
  628. message.offlineMessageRetryCount = (++retryCount);
  629. }];
  630. [userInfo setObject:@(YES) forKey:@"isOfflineMessage"];
  631. }
  632. }
  633. [NCUtils log:[NSString stringWithFormat:@"Could not send chat message. Error: %@", error.description]];
  634. } else {
  635. [[NCIntentController sharedInstance] donateSendMessageIntentForRoom:self->_room];
  636. }
  637. [[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidSendChatMessageNotification
  638. object:self
  639. userInfo:userInfo];
  640. [bgTask stopBackgroundTask];
  641. }];
  642. }
  643. - (void)sendChatMessage:(NCChatMessage *)message
  644. {
  645. [self sendChatMessage:message.sendingMessage replyTo:message.parentMessageId referenceId:message.referenceId silently:message.isSilent];
  646. }
  647. - (void)checkLastCommonReadMessage:(NSInteger)lastCommonReadMessage
  648. {
  649. if (lastCommonReadMessage > 0) {
  650. BOOL newerCommonReadReceived = lastCommonReadMessage > self->_room.lastCommonReadMessage;
  651. if (newerCommonReadReceived) {
  652. self->_room.lastCommonReadMessage = lastCommonReadMessage;
  653. [[NCRoomsManager sharedInstance] updateLastCommonReadMessage:lastCommonReadMessage forRoom:self->_room];
  654. NSMutableDictionary *userInfo = [NSMutableDictionary new];
  655. [userInfo setObject:self->_room.token forKey:@"room"];
  656. [userInfo setObject:@(lastCommonReadMessage) forKey:@"lastCommonReadMessage"];
  657. [[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveNewerCommonReadMessageNotification
  658. object:self
  659. userInfo:userInfo];
  660. }
  661. }
  662. }
  663. - (BOOL)isChatBeingBlocked:(NSInteger)statusCode
  664. {
  665. if (statusCode == 412) {
  666. return YES;
  667. }
  668. return NO;
  669. }
  670. - (void)notifyChatIsBlocked
  671. {
  672. NSMutableDictionary *userInfo = [NSMutableDictionary new];
  673. [userInfo setObject:_room.token forKey:@"room"];
  674. [[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveChatBlockedNotification
  675. object:self
  676. userInfo:userInfo];
  677. }
  678. - (void)stopChatController
  679. {
  680. [self stopReceivingNewChatMessages];
  681. [self stopReceivingChatHistory];
  682. self.hasReceivedMessagesFromServer = NO;
  683. }
  684. - (void)clearHistoryAndResetChatController
  685. {
  686. [_pullMessagesTask cancel];
  687. [self removeAllStoredMessagesAndChatBlocks];
  688. _room.lastReadMessage = 0;
  689. }
  690. - (BOOL)hasHistoryFromMessageId:(NSInteger)messageId
  691. {
  692. NCChatBlock *firstChatBlock = [self chatBlocksForRoom].firstObject;
  693. if (firstChatBlock && firstChatBlock.oldestMessageId == messageId) {
  694. return firstChatBlock.hasHistory;
  695. }
  696. return YES;
  697. }
  698. - (void)getMessageContextForMessageId:(NSInteger)messageId withLimit:(NSInteger)limit withCompletionBlock:(GetMessagesContextCompletionBlock)block
  699. {
  700. [[NCAPIController sharedInstance] getMessageContextInRoom:self.room.token forMessageId:messageId withLimit:limit forAccount:self.account withCompletionBlock:^(NSArray *messages, NSError *error, NSInteger statusCode) {
  701. if (error) {
  702. if (block) {
  703. block(nil);
  704. }
  705. return;
  706. }
  707. NSMutableArray *chatMessages = [[NSMutableArray alloc] initWithCapacity:messages.count];
  708. for (NSDictionary *messageDict in messages) {
  709. NCChatMessage *message = [NCChatMessage messageWithDictionary:messageDict andAccountId:self.account.accountId];
  710. [chatMessages addObject:message];
  711. if (!message.file) {
  712. continue;
  713. }
  714. // Try to get the stored preview height from our database, when the message is already stored
  715. NCChatMessage *managedMessage = [NCChatMessage objectsWhere:@"internalId = %@", message.internalId].firstObject;
  716. if (managedMessage && managedMessage.file && managedMessage.file.previewImageHeight > 0) {
  717. message.file.previewImageHeight = managedMessage.file.previewImageHeight;
  718. }
  719. }
  720. if (block) {
  721. block(chatMessages);
  722. }
  723. }];
  724. }
  725. @end