ChatSecure-iOS/ChatSecure/Classes/Controllers/OTRDatabaseView.m

419 lines
20 KiB
Objective-C

//
// OTRDatabaseView.m
// Off the Record
//
// Created by David Chiles on 3/31/14.
// Copyright (c) 2014 Chris Ballinger. All rights reserved.
//
#import "OTRDatabaseView.h"
@import YapDatabase;
#import "OTRDatabaseManager.h"
#import "OTRBuddy.h"
#import "OTRAccount.h"
#import "OTRIncomingMessage.h"
#import "OTRLog.h"
#import "OTROutgoingMessage.h"
#import <ChatSecureCore/ChatSecureCore-Swift.h>
NSString *OTRArchiveFilteredConversationsName = @"OTRFilteredConversationsName";
NSString *OTRBuddyFilteredConversationsName = @"OTRBuddyFilteredConversationsName";
NSString *OTRConversationGroup = @"Conversation";
NSString *OTRConversationDatabaseViewExtensionName = @"OTRConversationDatabaseViewExtensionName";
NSString *OTRChatDatabaseViewExtensionName = @"OTRChatDatabaseViewExtensionName";
NSString *OTRFilteredChatDatabaseViewExtensionName = @"OTRFilteredChatDatabaseViewExtensionName";
NSString *OTRAllBuddiesDatabaseViewExtensionName = @"OTRAllBuddiesDatabaseViewExtensionName";
NSString *OTRArchiveFilteredBuddiesName = @"OTRFilteredBuddiesName";
NSString *OTRAllSubscriptionRequestsViewExtensionName = @"AllSubscriptionRequestsViewExtensionName";
NSString *OTRAllPushAccountInfoViewExtensionName = @"OTRAllPushAccountInfoViewExtensionName";
NSString *OTRAllAccountGroup = @"All Accounts";
NSString *OTRAllAccountDatabaseViewExtensionName = @"OTRAllAccountDatabaseViewExtensionName";
NSString *OTRChatMessageGroup = @"Messages";
NSString *OTRBuddyGroup = @"Buddy";
NSString *OTRAllPresenceSubscriptionRequestGroup = @"OTRAllPresenceSubscriptionRequestGroup";
NSString *OTRUnreadMessageGroup = @"Unread Messages";
NSString *OTRPushTokenGroup = @"Tokens";
NSString *OTRPushDeviceGroup = @"Devices";
NSString *OTRPushAccountGroup = @"Account";
@implementation OTRDatabaseView
+ (BOOL)registerArchiveFilteredConversationsViewWithDatabase:(YapDatabase *)database {
YapDatabaseFilteredView *filteredView = [database registeredExtension:OTRArchiveFilteredConversationsName];
if (filteredView) {
return YES;
}
YapDatabaseView *conversationView = [database registeredExtension:OTRConversationDatabaseViewExtensionName];
if (!conversationView) {
return NO;
}
YapDatabaseViewOptions *options = [[YapDatabaseViewOptions alloc] init];
options.isPersistent = NO;
BOOL showArchived = NO;
YapDatabaseViewFiltering *filtering = [YapDatabaseViewFiltering withObjectBlock:^BOOL(YapDatabaseReadTransaction * _Nonnull transaction, NSString * _Nonnull group, NSString * _Nonnull collection, NSString * _Nonnull key, id _Nonnull object) {
if ([object conformsToProtocol:@protocol(OTRThreadOwner)]) {
id<OTRThreadOwner> threadOwner = object;
BOOL isArchived = threadOwner.isArchived;
return showArchived == isArchived;
}
return YES;
}];
filteredView = [[YapDatabaseFilteredView alloc] initWithParentViewName:OTRConversationDatabaseViewExtensionName filtering:filtering versionTag:[NSUUID UUID].UUIDString options:options];
return [database registerExtension:filteredView withName:OTRArchiveFilteredConversationsName];
}
+ (BOOL)registerBuddyFilteredConversationsViewWithDatabase:(YapDatabase *)database {
YapDatabaseFilteredView *filteredView = [database registeredExtension:OTRBuddyFilteredConversationsName];
if (filteredView) {
return YES;
}
YapDatabaseView *conversationView = [database registeredExtension:OTRConversationDatabaseViewExtensionName];
if (!conversationView) {
return NO;
}
YapDatabaseViewOptions *options = [[YapDatabaseViewOptions alloc] init];
options.isPersistent = YES;
NSSet *whiteListSet = [NSSet setWithObjects:[OTRBuddy collection], nil];
options.allowedCollections = [[YapWhitelistBlacklist alloc] initWithWhitelist:whiteListSet];
YapDatabaseViewFiltering *filtering = [YapDatabaseViewFiltering withObjectBlock:^BOOL(YapDatabaseReadTransaction * _Nonnull transaction, NSString * _Nonnull group, NSString * _Nonnull collection, NSString * _Nonnull key, id _Nonnull object) {
id<OTRThreadOwner> thread = (id<OTRThreadOwner>)object;
if (![thread conformsToProtocol:@protocol(OTRThreadOwner)]) {
return NO;
}
id<OTRMessageProtocol> lastMessage = [thread lastMessageWithTransaction:transaction];
if (!lastMessage) {
return NO;
}
return YES;
}];
filteredView = [[YapDatabaseFilteredView alloc] initWithParentViewName:OTRConversationDatabaseViewExtensionName filtering:filtering versionTag:@"1" options:options];
return [database registerExtension:filteredView withName:OTRBuddyFilteredConversationsName];
}
+ (BOOL)registerConversationDatabaseViewWithDatabase:(YapDatabase *)database
{
YapDatabaseView *conversationView = [database registeredExtension:OTRConversationDatabaseViewExtensionName];
if (conversationView) {
return YES;
}
YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *(YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) {
if ([object conformsToProtocol:@protocol(OTRThreadOwner)]) {
if ([object isKindOfClass:[OTRXMPPBuddy class]] && ((OTRXMPPBuddy *)object).askingForApproval) {
return OTRAllPresenceSubscriptionRequestGroup;
}
if ([object isKindOfClass:[OTRBuddy class]])
{
OTRBuddy *buddy = (OTRBuddy *)object;
if (!buddy.username.length) {
return nil;
}
// Hack to show "placeholder" items in list
if (buddy.lastMessageId && buddy.lastMessageId.length == 0) {
return OTRConversationGroup;
}
id <OTRMessageProtocol> lastMessage = [buddy lastMessageWithTransaction:transaction];
if (lastMessage) {
return OTRConversationGroup;
}
} else {
return OTRConversationGroup;
}
}
return nil; // exclude from view
}];
YapDatabaseViewSorting *viewSorting = [YapDatabaseViewSorting withObjectBlock:^NSComparisonResult(YapDatabaseReadTransaction *transaction, NSString *group, NSString *collection1, NSString *key1, id object1, NSString *collection2, NSString *key2, id object2) {
if ([group isEqualToString:OTRConversationGroup]) {
if ([object1 conformsToProtocol:@protocol(OTRThreadOwner)] && [object2 conformsToProtocol:@protocol(OTRThreadOwner)]) {
id <OTRThreadOwner> thread1 = object1;
id <OTRThreadOwner> thread2 = object2;
id <OTRMessageProtocol> message1 = [thread1 lastMessageWithTransaction:transaction];
id <OTRMessageProtocol> message2 = [thread2 lastMessageWithTransaction:transaction];
// Assume nil dates indicate a lastMessageId of ""
// indicating that we want to force to the top
NSDate *date1 = [message1 messageDate];
if (!date1) {
if (thread1.lastMessageIdentifier && thread1.lastMessageIdentifier.length == 0) {
date1 = [NSDate date];
} else {
date1 = [NSDate distantPast];
}
}
NSDate *date2 = [message2 messageDate];
if (!date2) {
if (thread2.lastMessageIdentifier && thread2.lastMessageIdentifier.length == 0) {
date2 = [NSDate date];
} else {
date2 = [NSDate distantPast];
}
}
return [date2 compare:date1];
}
} else if ([group isEqualToString:OTRAllPresenceSubscriptionRequestGroup]) {
if ([object1 isKindOfClass:[OTRXMPPBuddy class]] && [object2 isKindOfClass:[OTRXMPPBuddy class]]) {
OTRXMPPBuddy *request1 = object1;
OTRXMPPBuddy *request2 = object2;
return [request2.displayName compare:request1.displayName];
}
}
return NSOrderedSame;
}];
YapDatabaseViewOptions *options = [[YapDatabaseViewOptions alloc] init];
options.isPersistent = YES;
NSSet *whiteListSet = [NSSet setWithObjects:[OTRBuddy collection],[OTRXMPPRoom collection], nil];
options.allowedCollections = [[YapWhitelistBlacklist alloc] initWithWhitelist:whiteListSet];
YapDatabaseAutoView *databaseView = [[YapDatabaseAutoView alloc] initWithGrouping:viewGrouping
sorting:viewSorting
versionTag:@"9"
options:options];
BOOL result = [database registerExtension:databaseView withName:OTRConversationDatabaseViewExtensionName];
if (result) {
result = [self registerArchiveFilteredConversationsViewWithDatabase:database];
}
if (result) {
result = [self registerBuddyFilteredConversationsViewWithDatabase:database];
}
return result;
}
+ (BOOL)registerAllAccountsDatabaseViewWithDatabase:(YapDatabase *)database
{
YapDatabaseView *accountView = [database registeredExtension:OTRAllAccountDatabaseViewExtensionName];
if (accountView) {
return YES;
}
[YapDatabaseViewGrouping withObjectBlock:^NSString * _Nullable(YapDatabaseReadTransaction * _Nonnull transaction, NSString * _Nonnull collection, NSString * _Nonnull key, id _Nonnull object) {
if ([collection isEqualToString:[OTRAccount collection]] && [object isKindOfClass:[OTRAccount class]])
{
OTRAccount *account = object;
if (!account.username.length) {
return nil;
}
return OTRAllAccountGroup;
}
return nil;
}];
YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withKeyBlock:^NSString *(YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key) {
if ([collection isEqualToString:[OTRAccount collection]])
{
return OTRAllAccountGroup;
}
return nil;
}];
YapDatabaseViewSorting *viewSorting = [YapDatabaseViewSorting withObjectBlock:^NSComparisonResult(YapDatabaseReadTransaction *transaction, NSString *group, NSString *collection1, NSString *key1, id object1, NSString *collection2, NSString *key2, id object2) {
if ([group isEqualToString:OTRAllAccountGroup]) {
if ([object1 isKindOfClass:[OTRAccount class]] && [object2 isKindOfClass:[OTRAccount class]]) {
OTRAccount *account1 = (OTRAccount *)object1;
OTRAccount *account2 = (OTRAccount *)object2;
return [account1.displayName compare:account2.displayName options:NSCaseInsensitiveSearch];
}
}
return NSOrderedSame;
}];
YapDatabaseViewOptions *options = [[YapDatabaseViewOptions alloc] init];
options.isPersistent = YES;
options.allowedCollections = [[YapWhitelistBlacklist alloc] initWithWhitelist:[NSSet setWithObject:[OTRAccount collection]]];
YapDatabaseAutoView *databaseView = [[YapDatabaseAutoView alloc] initWithGrouping:viewGrouping
sorting:viewSorting
versionTag:@"2"
options:options];
return [database registerExtension:databaseView withName:OTRAllAccountDatabaseViewExtensionName];
}
+ (BOOL)registerFilteredChatViewWithDatabase:(YapDatabase *)database {
YapDatabaseFilteredView *filteredView = [database registeredExtension:OTRFilteredChatDatabaseViewExtensionName];
if (filteredView) {
return YES;
}
YapDatabaseView *chatView = [database registeredExtension:OTRChatDatabaseViewExtensionName];
if (!chatView) {
return NO;
}
YapDatabaseViewOptions *options = [[YapDatabaseViewOptions alloc] init];
YapDatabaseViewFiltering *filtering = [YapDatabaseViewFiltering withObjectBlock:^BOOL(YapDatabaseReadTransaction * _Nonnull transaction, NSString * _Nonnull group, NSString * _Nonnull collection, NSString * _Nonnull key, id _Nonnull object) {
if ([object conformsToProtocol:@protocol(OTRMessageProtocol)]) {
id<OTRMessageProtocol> message = object;
BOOL shouldDisplay = [FileTransferManager shouldDisplayMessage:message transaction:transaction];
return shouldDisplay;
}
return YES;
}];
filteredView = [[YapDatabaseFilteredView alloc] initWithParentViewName:OTRChatDatabaseViewExtensionName filtering:filtering versionTag:@"6" options:options];
return [database registerExtension:filteredView withName:OTRFilteredChatDatabaseViewExtensionName];
}
+ (BOOL)registerChatDatabaseViewWithDatabase:(YapDatabase *)database
{
if ([database registeredExtension:OTRChatDatabaseViewExtensionName]) {
return YES;
}
YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *(YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) {
if ([object conformsToProtocol:@protocol(OTRMessageProtocol)])
{
id <OTRMessageProtocol> message = object;
NSString *threadId = [message threadId];
if (!threadId) {
DDLogError(@"Message has no threadId! %@", message);
return nil;
} else {
return threadId;
}
} else {
DDLogError(@"Object in view does not conform to OTRMessageProtocol! %@", object);
return nil;
}
}];
YapDatabaseViewSorting *viewSorting = [YapDatabaseViewSorting withObjectBlock:^NSComparisonResult(YapDatabaseReadTransaction *transaction, NSString *group, NSString *collection1, NSString *key1, id object1, NSString *collection2, NSString *key2, id object2) {
if ([object1 conformsToProtocol:@protocol(OTRMessageProtocol)] && [object2 conformsToProtocol:@protocol(OTRMessageProtocol)]) {
id <OTRMessageProtocol> message1 = (id <OTRMessageProtocol>)object1;
id <OTRMessageProtocol> message2 = (id <OTRMessageProtocol>)object2;
return [[message1 messageDate] compare:[message2 messageDate]];
}
return NSOrderedSame;
}];
YapDatabaseViewOptions *options = [[YapDatabaseViewOptions alloc] init];
options.isPersistent = YES;
NSSet *whitelist = [NSSet setWithObjects:[OTRBaseMessage collection],[OTRXMPPRoomMessage collection], nil];
options.allowedCollections = [[YapWhitelistBlacklist alloc] initWithWhitelist:whitelist];
YapDatabaseAutoView *view = [[YapDatabaseAutoView alloc] initWithGrouping:viewGrouping
sorting:viewSorting
versionTag:@"1"
options:options];
return [database registerExtension:view withName:OTRChatDatabaseViewExtensionName] && [self registerFilteredChatViewWithDatabase:database];
}
+ (BOOL)registerAllBuddiesDatabaseViewWithDatabase:(YapDatabase *)database
{
if ([database registeredExtension:OTRAllBuddiesDatabaseViewExtensionName]) {
return YES;
}
YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *(YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) {
if ([object isKindOfClass:[OTRBuddy class]]) {
//Checking to see if the buddy username is equal to the account username in order to remove 'self' buddy
OTRBuddy *buddy = (OTRBuddy *)object;
OTRAccount *account = [buddy accountWithTransaction:transaction];
// Hack fix for buddies created without an account
// There must be a race condition in the roster popualtion
if (!account) {
return nil;
}
// Filter out buddies with no username
if (!buddy.username.length) {
return nil;
}
if (![account.username isEqualToString:buddy.username]) {
// Filter out buddies that are not really on our roster
if ([buddy isKindOfClass:[OTRXMPPBuddy class]]) {
OTRXMPPBuddy *xmppBuddy = (OTRXMPPBuddy *)buddy;
if (xmppBuddy.trustLevel != BuddyTrustLevelRoster && !xmppBuddy.pendingApproval) {
return nil;
}
}
return OTRBuddyGroup;
}
}
return nil;
}];
YapDatabaseViewSorting *viewSorting = [YapDatabaseViewSorting withObjectBlock:^NSComparisonResult(YapDatabaseReadTransaction *transaction, NSString *group, NSString *collection1, NSString *key1, id object1, NSString *collection2, NSString *key2, id object2) {
OTRBuddy *buddy1 = (OTRBuddy *)object1;
OTRBuddy *buddy2 = (OTRBuddy *)object2;
NSComparisonResult result = NSOrderedSame;
if (buddy1.currentStatus == buddy2.currentStatus) {
NSString *buddy1String = buddy1.username;
NSString *buddy2String = buddy2.username;
if ([buddy1.displayName length]) {
buddy1String = buddy1.displayName;
}
if ([buddy2.displayName length]) {
buddy2String = buddy2.displayName;
}
result = [buddy1String compare:buddy2String options:NSCaseInsensitiveSearch];
}
else if (buddy1.currentStatus < buddy2.currentStatus) {
result = NSOrderedAscending;
}
else {
result = NSOrderedDescending;
}
NSComparisonResult archiveSort = [@(buddy1.isArchived) compare:@(buddy2.isArchived)];
if (archiveSort == NSOrderedSame) {
return result;
} else {
return archiveSort;
}
}];
YapDatabaseViewOptions *options = [[YapDatabaseViewOptions alloc] init];
options.isPersistent = YES;
options.allowedCollections = [[YapWhitelistBlacklist alloc] initWithWhitelist:[NSSet setWithObject:[OTRBuddy collection]]];
YapDatabaseAutoView *view = [[YapDatabaseAutoView alloc] initWithGrouping:viewGrouping
sorting:viewSorting
versionTag:@"9"
options:options];
return [database registerExtension:view withName:OTRAllBuddiesDatabaseViewExtensionName] && [self registerFilteredBuddiesViewWithDatabase:database];
}
+ (BOOL)registerFilteredBuddiesViewWithDatabase:(YapDatabase *)database {
YapDatabaseFilteredView *filteredView = [database registeredExtension:OTRArchiveFilteredBuddiesName];
if (filteredView) {
return YES;
}
YapDatabaseView *buddiesView = [database registeredExtension:OTRAllBuddiesDatabaseViewExtensionName];
if (!buddiesView) {
return NO;
}
YapDatabaseViewOptions *options = [[YapDatabaseViewOptions alloc] init];
options.isPersistent = NO;
BOOL showArchived = NO;
YapDatabaseViewFiltering *filtering = [YapDatabaseViewFiltering withObjectBlock:^BOOL(YapDatabaseReadTransaction * _Nonnull transaction, NSString * _Nonnull group, NSString * _Nonnull collection, NSString * _Nonnull key, id _Nonnull object) {
if ([object conformsToProtocol:@protocol(OTRThreadOwner)]) {
id<OTRThreadOwner> threadOwner = object;
BOOL isArchived = threadOwner.isArchived;
return showArchived == isArchived;
}
return YES;
}];
filteredView = [[YapDatabaseFilteredView alloc] initWithParentViewName:OTRAllBuddiesDatabaseViewExtensionName filtering:filtering versionTag:[NSUUID UUID].UUIDString options:options];
return [database registerExtension:filteredView withName:OTRArchiveFilteredBuddiesName];
}
@end