/ChatSecureCore/Classes/Controllers/OTRDatabaseView.m

https://github.com/chrisballinger/ChatSecure-iOS · Objective C · 418 lines · 351 code · 52 blank · 15 comment · 81 complexity · 48742e5cc658a6303153923092c68eba MD5 · raw file

  1. //
  2. // OTRDatabaseView.m
  3. // Off the Record
  4. //
  5. // Created by David Chiles on 3/31/14.
  6. // Copyright (c) 2014 Chris Ballinger. All rights reserved.
  7. //
  8. #import "OTRDatabaseView.h"
  9. @import YapDatabase;
  10. #import "OTRDatabaseManager.h"
  11. #import "OTRBuddy.h"
  12. #import "OTRAccount.h"
  13. #import "OTRIncomingMessage.h"
  14. #import "OTRLog.h"
  15. #import "OTROutgoingMessage.h"
  16. #import "ChatSecureCoreCompat-Swift.h"
  17. NSString *OTRArchiveFilteredConversationsName = @"OTRFilteredConversationsName";
  18. NSString *OTRBuddyFilteredConversationsName = @"OTRBuddyFilteredConversationsName";
  19. NSString *OTRConversationGroup = @"Conversation";
  20. NSString *OTRConversationDatabaseViewExtensionName = @"OTRConversationDatabaseViewExtensionName";
  21. NSString *OTRChatDatabaseViewExtensionName = @"OTRChatDatabaseViewExtensionName";
  22. NSString *OTRFilteredChatDatabaseViewExtensionName = @"OTRFilteredChatDatabaseViewExtensionName";
  23. NSString *OTRAllBuddiesDatabaseViewExtensionName = @"OTRAllBuddiesDatabaseViewExtensionName";
  24. NSString *OTRArchiveFilteredBuddiesName = @"OTRFilteredBuddiesName";
  25. NSString *OTRAllSubscriptionRequestsViewExtensionName = @"AllSubscriptionRequestsViewExtensionName";
  26. NSString *OTRAllPushAccountInfoViewExtensionName = @"OTRAllPushAccountInfoViewExtensionName";
  27. NSString *OTRAllAccountGroup = @"All Accounts";
  28. NSString *OTRAllAccountDatabaseViewExtensionName = @"OTRAllAccountDatabaseViewExtensionName";
  29. NSString *OTRChatMessageGroup = @"Messages";
  30. NSString *OTRBuddyGroup = @"Buddy";
  31. NSString *OTRAllPresenceSubscriptionRequestGroup = @"OTRAllPresenceSubscriptionRequestGroup";
  32. NSString *OTRUnreadMessageGroup = @"Unread Messages";
  33. NSString *OTRPushTokenGroup = @"Tokens";
  34. NSString *OTRPushDeviceGroup = @"Devices";
  35. NSString *OTRPushAccountGroup = @"Account";
  36. @implementation OTRDatabaseView
  37. + (BOOL)registerArchiveFilteredConversationsViewWithDatabase:(YapDatabase *)database {
  38. YapDatabaseFilteredView *filteredView = [database registeredExtension:OTRArchiveFilteredConversationsName];
  39. if (filteredView) {
  40. return YES;
  41. }
  42. YapDatabaseView *conversationView = [database registeredExtension:OTRConversationDatabaseViewExtensionName];
  43. if (!conversationView) {
  44. return NO;
  45. }
  46. YapDatabaseViewOptions *options = [[YapDatabaseViewOptions alloc] init];
  47. options.isPersistent = NO;
  48. BOOL showArchived = NO;
  49. YapDatabaseViewFiltering *filtering = [YapDatabaseViewFiltering withObjectBlock:^BOOL(YapDatabaseReadTransaction * _Nonnull transaction, NSString * _Nonnull group, NSString * _Nonnull collection, NSString * _Nonnull key, id _Nonnull object) {
  50. if ([object conformsToProtocol:@protocol(OTRThreadOwner)]) {
  51. id<OTRThreadOwner> threadOwner = object;
  52. BOOL isArchived = threadOwner.isArchived;
  53. return showArchived == isArchived;
  54. }
  55. return YES;
  56. }];
  57. filteredView = [[YapDatabaseFilteredView alloc] initWithParentViewName:OTRConversationDatabaseViewExtensionName filtering:filtering versionTag:[NSUUID UUID].UUIDString options:options];
  58. return [database registerExtension:filteredView withName:OTRArchiveFilteredConversationsName];
  59. }
  60. + (BOOL)registerBuddyFilteredConversationsViewWithDatabase:(YapDatabase *)database {
  61. YapDatabaseFilteredView *filteredView = [database registeredExtension:OTRBuddyFilteredConversationsName];
  62. if (filteredView) {
  63. return YES;
  64. }
  65. YapDatabaseView *conversationView = [database registeredExtension:OTRConversationDatabaseViewExtensionName];
  66. if (!conversationView) {
  67. return NO;
  68. }
  69. YapDatabaseViewOptions *options = [[YapDatabaseViewOptions alloc] init];
  70. options.isPersistent = YES;
  71. NSSet *whiteListSet = [NSSet setWithObjects:[OTRBuddy collection], nil];
  72. options.allowedCollections = [[YapWhitelistBlacklist alloc] initWithWhitelist:whiteListSet];
  73. YapDatabaseViewFiltering *filtering = [YapDatabaseViewFiltering withObjectBlock:^BOOL(YapDatabaseReadTransaction * _Nonnull transaction, NSString * _Nonnull group, NSString * _Nonnull collection, NSString * _Nonnull key, id _Nonnull object) {
  74. id<OTRThreadOwner> thread = (id<OTRThreadOwner>)object;
  75. if (![thread conformsToProtocol:@protocol(OTRThreadOwner)]) {
  76. return NO;
  77. }
  78. id<OTRMessageProtocol> lastMessage = [thread lastMessageWithTransaction:transaction];
  79. if (!lastMessage) {
  80. return NO;
  81. }
  82. return YES;
  83. }];
  84. filteredView = [[YapDatabaseFilteredView alloc] initWithParentViewName:OTRConversationDatabaseViewExtensionName filtering:filtering versionTag:@"1" options:options];
  85. return [database registerExtension:filteredView withName:OTRBuddyFilteredConversationsName];
  86. }
  87. + (BOOL)registerConversationDatabaseViewWithDatabase:(YapDatabase *)database
  88. {
  89. YapDatabaseView *conversationView = [database registeredExtension:OTRConversationDatabaseViewExtensionName];
  90. if (conversationView) {
  91. return YES;
  92. }
  93. YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *(YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) {
  94. if ([object conformsToProtocol:@protocol(OTRThreadOwner)]) {
  95. if ([object isKindOfClass:[OTRXMPPBuddy class]] && ((OTRXMPPBuddy *)object).askingForApproval) {
  96. return OTRAllPresenceSubscriptionRequestGroup;
  97. }
  98. if ([object isKindOfClass:[OTRBuddy class]])
  99. {
  100. OTRBuddy *buddy = (OTRBuddy *)object;
  101. if (!buddy.username.length) {
  102. return nil;
  103. }
  104. // Hack to show "placeholder" items in list
  105. if (buddy.lastMessageId && buddy.lastMessageId.length == 0) {
  106. return OTRConversationGroup;
  107. }
  108. id <OTRMessageProtocol> lastMessage = [buddy lastMessageWithTransaction:transaction];
  109. if (lastMessage) {
  110. return OTRConversationGroup;
  111. }
  112. } else {
  113. return OTRConversationGroup;
  114. }
  115. }
  116. return nil; // exclude from view
  117. }];
  118. YapDatabaseViewSorting *viewSorting = [YapDatabaseViewSorting withObjectBlock:^NSComparisonResult(YapDatabaseReadTransaction *transaction, NSString *group, NSString *collection1, NSString *key1, id object1, NSString *collection2, NSString *key2, id object2) {
  119. if ([group isEqualToString:OTRConversationGroup]) {
  120. if ([object1 conformsToProtocol:@protocol(OTRThreadOwner)] && [object2 conformsToProtocol:@protocol(OTRThreadOwner)]) {
  121. id <OTRThreadOwner> thread1 = object1;
  122. id <OTRThreadOwner> thread2 = object2;
  123. id <OTRMessageProtocol> message1 = [thread1 lastMessageWithTransaction:transaction];
  124. id <OTRMessageProtocol> message2 = [thread2 lastMessageWithTransaction:transaction];
  125. // Assume nil dates indicate a lastMessageId of ""
  126. // indicating that we want to force to the top
  127. NSDate *date1 = [message1 messageDate];
  128. if (!date1) {
  129. if (thread1.lastMessageIdentifier && thread1.lastMessageIdentifier.length == 0) {
  130. date1 = [NSDate date];
  131. } else {
  132. date1 = [NSDate distantPast];
  133. }
  134. }
  135. NSDate *date2 = [message2 messageDate];
  136. if (!date2) {
  137. if (thread2.lastMessageIdentifier && thread2.lastMessageIdentifier.length == 0) {
  138. date2 = [NSDate date];
  139. } else {
  140. date2 = [NSDate distantPast];
  141. }
  142. }
  143. return [date2 compare:date1];
  144. }
  145. } else if ([group isEqualToString:OTRAllPresenceSubscriptionRequestGroup]) {
  146. if ([object1 isKindOfClass:[OTRXMPPBuddy class]] && [object2 isKindOfClass:[OTRXMPPBuddy class]]) {
  147. OTRXMPPBuddy *request1 = object1;
  148. OTRXMPPBuddy *request2 = object2;
  149. return [request2.displayName compare:request1.displayName];
  150. }
  151. }
  152. return NSOrderedSame;
  153. }];
  154. YapDatabaseViewOptions *options = [[YapDatabaseViewOptions alloc] init];
  155. options.isPersistent = YES;
  156. NSSet *whiteListSet = [NSSet setWithObjects:[OTRBuddy collection],[OTRXMPPRoom collection], nil];
  157. options.allowedCollections = [[YapWhitelistBlacklist alloc] initWithWhitelist:whiteListSet];
  158. YapDatabaseAutoView *databaseView = [[YapDatabaseAutoView alloc] initWithGrouping:viewGrouping
  159. sorting:viewSorting
  160. versionTag:@"9"
  161. options:options];
  162. BOOL result = [database registerExtension:databaseView withName:OTRConversationDatabaseViewExtensionName];
  163. if (result) {
  164. result = [self registerArchiveFilteredConversationsViewWithDatabase:database];
  165. }
  166. if (result) {
  167. result = [self registerBuddyFilteredConversationsViewWithDatabase:database];
  168. }
  169. return result;
  170. }
  171. + (BOOL)registerAllAccountsDatabaseViewWithDatabase:(YapDatabase *)database
  172. {
  173. YapDatabaseView *accountView = [database registeredExtension:OTRAllAccountDatabaseViewExtensionName];
  174. if (accountView) {
  175. return YES;
  176. }
  177. [YapDatabaseViewGrouping withObjectBlock:^NSString * _Nullable(YapDatabaseReadTransaction * _Nonnull transaction, NSString * _Nonnull collection, NSString * _Nonnull key, id _Nonnull object) {
  178. if ([collection isEqualToString:[OTRAccount collection]] && [object isKindOfClass:[OTRAccount class]])
  179. {
  180. OTRAccount *account = object;
  181. if (!account.username.length) {
  182. return nil;
  183. }
  184. return OTRAllAccountGroup;
  185. }
  186. return nil;
  187. }];
  188. YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withKeyBlock:^NSString *(YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key) {
  189. if ([collection isEqualToString:[OTRAccount collection]])
  190. {
  191. return OTRAllAccountGroup;
  192. }
  193. return nil;
  194. }];
  195. YapDatabaseViewSorting *viewSorting = [YapDatabaseViewSorting withObjectBlock:^NSComparisonResult(YapDatabaseReadTransaction *transaction, NSString *group, NSString *collection1, NSString *key1, id object1, NSString *collection2, NSString *key2, id object2) {
  196. if ([group isEqualToString:OTRAllAccountGroup]) {
  197. if ([object1 isKindOfClass:[OTRAccount class]] && [object2 isKindOfClass:[OTRAccount class]]) {
  198. OTRAccount *account1 = (OTRAccount *)object1;
  199. OTRAccount *account2 = (OTRAccount *)object2;
  200. return [account1.displayName compare:account2.displayName options:NSCaseInsensitiveSearch];
  201. }
  202. }
  203. return NSOrderedSame;
  204. }];
  205. YapDatabaseViewOptions *options = [[YapDatabaseViewOptions alloc] init];
  206. options.isPersistent = YES;
  207. options.allowedCollections = [[YapWhitelistBlacklist alloc] initWithWhitelist:[NSSet setWithObject:[OTRAccount collection]]];
  208. YapDatabaseAutoView *databaseView = [[YapDatabaseAutoView alloc] initWithGrouping:viewGrouping
  209. sorting:viewSorting
  210. versionTag:@"2"
  211. options:options];
  212. return [database registerExtension:databaseView withName:OTRAllAccountDatabaseViewExtensionName];
  213. }
  214. + (BOOL)registerFilteredChatViewWithDatabase:(YapDatabase *)database {
  215. YapDatabaseFilteredView *filteredView = [database registeredExtension:OTRFilteredChatDatabaseViewExtensionName];
  216. if (filteredView) {
  217. return YES;
  218. }
  219. YapDatabaseView *chatView = [database registeredExtension:OTRChatDatabaseViewExtensionName];
  220. if (!chatView) {
  221. return NO;
  222. }
  223. YapDatabaseViewOptions *options = [[YapDatabaseViewOptions alloc] init];
  224. YapDatabaseViewFiltering *filtering = [YapDatabaseViewFiltering withObjectBlock:^BOOL(YapDatabaseReadTransaction * _Nonnull transaction, NSString * _Nonnull group, NSString * _Nonnull collection, NSString * _Nonnull key, id _Nonnull object) {
  225. if ([object conformsToProtocol:@protocol(OTRMessageProtocol)]) {
  226. id<OTRMessageProtocol> message = object;
  227. BOOL shouldDisplay = [FileTransferManager shouldDisplayMessage:message transaction:transaction];
  228. return shouldDisplay;
  229. }
  230. return YES;
  231. }];
  232. filteredView = [[YapDatabaseFilteredView alloc] initWithParentViewName:OTRChatDatabaseViewExtensionName filtering:filtering versionTag:@"6" options:options];
  233. return [database registerExtension:filteredView withName:OTRFilteredChatDatabaseViewExtensionName];
  234. }
  235. + (BOOL)registerChatDatabaseViewWithDatabase:(YapDatabase *)database
  236. {
  237. if ([database registeredExtension:OTRChatDatabaseViewExtensionName]) {
  238. return YES;
  239. }
  240. YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *(YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) {
  241. if ([object conformsToProtocol:@protocol(OTRMessageProtocol)])
  242. {
  243. id <OTRMessageProtocol> message = object;
  244. NSString *threadId = [message threadId];
  245. if (!threadId) {
  246. DDLogError(@"Message has no threadId! %@", message);
  247. return nil;
  248. } else {
  249. return threadId;
  250. }
  251. } else {
  252. DDLogError(@"Object in view does not conform to OTRMessageProtocol! %@", object);
  253. return nil;
  254. }
  255. }];
  256. YapDatabaseViewSorting *viewSorting = [YapDatabaseViewSorting withObjectBlock:^NSComparisonResult(YapDatabaseReadTransaction *transaction, NSString *group, NSString *collection1, NSString *key1, id object1, NSString *collection2, NSString *key2, id object2) {
  257. if ([object1 conformsToProtocol:@protocol(OTRMessageProtocol)] && [object2 conformsToProtocol:@protocol(OTRMessageProtocol)]) {
  258. id <OTRMessageProtocol> message1 = (id <OTRMessageProtocol>)object1;
  259. id <OTRMessageProtocol> message2 = (id <OTRMessageProtocol>)object2;
  260. return [[message1 messageDate] compare:[message2 messageDate]];
  261. }
  262. return NSOrderedSame;
  263. }];
  264. YapDatabaseViewOptions *options = [[YapDatabaseViewOptions alloc] init];
  265. options.isPersistent = YES;
  266. NSSet *whitelist = [NSSet setWithObjects:[OTRBaseMessage collection],[OTRXMPPRoomMessage collection], nil];
  267. options.allowedCollections = [[YapWhitelistBlacklist alloc] initWithWhitelist:whitelist];
  268. YapDatabaseAutoView *view = [[YapDatabaseAutoView alloc] initWithGrouping:viewGrouping
  269. sorting:viewSorting
  270. versionTag:@"1"
  271. options:options];
  272. return [database registerExtension:view withName:OTRChatDatabaseViewExtensionName] && [self registerFilteredChatViewWithDatabase:database];
  273. }
  274. + (BOOL)registerAllBuddiesDatabaseViewWithDatabase:(YapDatabase *)database
  275. {
  276. if ([database registeredExtension:OTRAllBuddiesDatabaseViewExtensionName]) {
  277. return YES;
  278. }
  279. YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *(YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) {
  280. if ([object isKindOfClass:[OTRBuddy class]]) {
  281. //Checking to see if the buddy username is equal to the account username in order to remove 'self' buddy
  282. OTRBuddy *buddy = (OTRBuddy *)object;
  283. OTRAccount *account = [buddy accountWithTransaction:transaction];
  284. // Hack fix for buddies created without an account
  285. // There must be a race condition in the roster popualtion
  286. if (!account) {
  287. return nil;
  288. }
  289. // Filter out buddies with no username
  290. if (!buddy.username.length) {
  291. return nil;
  292. }
  293. if (![account.username isEqualToString:buddy.username]) {
  294. // Filter out buddies that are not really on our roster
  295. if ([buddy isKindOfClass:[OTRXMPPBuddy class]]) {
  296. OTRXMPPBuddy *xmppBuddy = (OTRXMPPBuddy *)buddy;
  297. if (xmppBuddy.trustLevel != BuddyTrustLevelRoster && !xmppBuddy.pendingApproval) {
  298. return nil;
  299. }
  300. }
  301. return OTRBuddyGroup;
  302. }
  303. }
  304. return nil;
  305. }];
  306. YapDatabaseViewSorting *viewSorting = [YapDatabaseViewSorting withObjectBlock:^NSComparisonResult(YapDatabaseReadTransaction *transaction, NSString *group, NSString *collection1, NSString *key1, id object1, NSString *collection2, NSString *key2, id object2) {
  307. OTRBuddy *buddy1 = (OTRBuddy *)object1;
  308. OTRBuddy *buddy2 = (OTRBuddy *)object2;
  309. NSComparisonResult result = NSOrderedSame;
  310. if (buddy1.currentStatus == buddy2.currentStatus) {
  311. NSString *buddy1String = buddy1.username;
  312. NSString *buddy2String = buddy2.username;
  313. if ([buddy1.displayName length]) {
  314. buddy1String = buddy1.displayName;
  315. }
  316. if ([buddy2.displayName length]) {
  317. buddy2String = buddy2.displayName;
  318. }
  319. result = [buddy1String compare:buddy2String options:NSCaseInsensitiveSearch];
  320. }
  321. else if (buddy1.currentStatus < buddy2.currentStatus) {
  322. result = NSOrderedAscending;
  323. }
  324. else {
  325. result = NSOrderedDescending;
  326. }
  327. NSComparisonResult archiveSort = [@(buddy1.isArchived) compare:@(buddy2.isArchived)];
  328. if (archiveSort == NSOrderedSame) {
  329. return result;
  330. } else {
  331. return archiveSort;
  332. }
  333. }];
  334. YapDatabaseViewOptions *options = [[YapDatabaseViewOptions alloc] init];
  335. options.isPersistent = YES;
  336. options.allowedCollections = [[YapWhitelistBlacklist alloc] initWithWhitelist:[NSSet setWithObject:[OTRBuddy collection]]];
  337. YapDatabaseAutoView *view = [[YapDatabaseAutoView alloc] initWithGrouping:viewGrouping
  338. sorting:viewSorting
  339. versionTag:@"9"
  340. options:options];
  341. return [database registerExtension:view withName:OTRAllBuddiesDatabaseViewExtensionName] && [self registerFilteredBuddiesViewWithDatabase:database];
  342. }
  343. + (BOOL)registerFilteredBuddiesViewWithDatabase:(YapDatabase *)database {
  344. YapDatabaseFilteredView *filteredView = [database registeredExtension:OTRArchiveFilteredBuddiesName];
  345. if (filteredView) {
  346. return YES;
  347. }
  348. YapDatabaseView *buddiesView = [database registeredExtension:OTRAllBuddiesDatabaseViewExtensionName];
  349. if (!buddiesView) {
  350. return NO;
  351. }
  352. YapDatabaseViewOptions *options = [[YapDatabaseViewOptions alloc] init];
  353. options.isPersistent = NO;
  354. BOOL showArchived = NO;
  355. YapDatabaseViewFiltering *filtering = [YapDatabaseViewFiltering withObjectBlock:^BOOL(YapDatabaseReadTransaction * _Nonnull transaction, NSString * _Nonnull group, NSString * _Nonnull collection, NSString * _Nonnull key, id _Nonnull object) {
  356. if ([object conformsToProtocol:@protocol(OTRThreadOwner)]) {
  357. id<OTRThreadOwner> threadOwner = object;
  358. BOOL isArchived = threadOwner.isArchived;
  359. return showArchived == isArchived;
  360. }
  361. return YES;
  362. }];
  363. filteredView = [[YapDatabaseFilteredView alloc] initWithParentViewName:OTRAllBuddiesDatabaseViewExtensionName filtering:filtering versionTag:[NSUUID UUID].UUIDString options:options];
  364. return [database registerExtension:filteredView withName:OTRArchiveFilteredBuddiesName];
  365. }
  366. @end