talk-ios/NextcloudTalk/NCDatabaseManager.m

368 строки
16 KiB
Objective-C

/**
* @copyright Copyright (c) 2020 Ivan Sein <ivan@nextcloud.com>
*
* @author Ivan Sein <ivan@nextcloud.com>
*
* @license GNU GPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#import "NCDatabaseManager.h"
#import "ABContact.h"
#import "NCAppBranding.h"
#import "NCChatBlock.h"
#import "NCChatMessage.h"
#import "NCContact.h"
#import "NCRoom.h"
NSString *const kTalkDatabaseFolder = @"Library/Application Support/Talk";
NSString *const kTalkDatabaseFileName = @"talk.realm";
uint64_t const kTalkDatabaseSchemaVersion = 31;
NSString * const kCapabilitySystemMessages = @"system-messages";
NSString * const kCapabilityNotificationLevels = @"notification-levels";
NSString * const kCapabilityInviteGroupsAndMails = @"invite-groups-and-mails";
NSString * const kCapabilityLockedOneToOneRooms = @"locked-one-to-one-rooms";
NSString * const kCapabilityWebinaryLobby = @"webinary-lobby";
NSString * const kCapabilityChatReadMarker = @"chat-read-marker";
NSString * const kCapabilityStartCallFlag = @"start-call-flag";
NSString * const kCapabilityCirclesSupport = @"circles-support";
NSString * const kCapabilityChatReferenceId = @"chat-reference-id";
NSString * const kCapabilityPhonebookSearch = @"phonebook-search";
NSString * const kCapabilityChatReadStatus = @"chat-read-status";
NSString * const kCapabilityListableRooms = @"listable-rooms";
NSString * const kCapabilityDeleteMessages = @"delete-messages";
NSString * const kCapabilityCallFlags = @"conversation-call-flags";
NSString * const kCapabilityRoomDescription = @"room-description";
NSString * const kCapabilityTempUserAvatarAPI = @"temp-user-avatar-api";
NSString * const kCapabilityLocationSharing = @"geo-location-sharing";
NSString * const kCapabilityConversationV4 = @"conversation-v4";
NSString * const kCapabilitySIPSupport = @"sip-support";
NSString * const kCapabilityVoiceMessage = @"voice-message-sharing";
NSString * const kCapabilitySignalingV3 = @"signaling-v3";
NSString * const kCapabilityClearHistory = @"clear-history";
NSString * const kCapabilityDirectMentionFlag = @"direct-mention-flag";
NSString * const kCapabilityNotificationCalls = @"notification-calls";
NSString * const kCapabilityConversationPermissions = @"conversation-permissions";
NSString * const kCapabilityChatUnread = @"chat-unread";
NSString * const kCapabilityReactions = @"reactions";
NSString * const kCapabilityRichObjectDelete = @"rich-object-delete";
NSString * const kMinimumRequiredTalkCapability = kCapabilitySystemMessages; // Talk 4.0 is the minimum required version
@implementation NCDatabaseManager
+ (NCDatabaseManager *)sharedInstance
{
static dispatch_once_t once;
static NCDatabaseManager *sharedInstance;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (id)init
{
self = [super init];
if (self) {
// Create Talk database directory
NSString *path = [[[[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:groupIdentifier] URLByAppendingPathComponent:kTalkDatabaseFolder] path];
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
[[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
}
[[NSFileManager defaultManager] setAttributes:@{NSFileProtectionKey:NSFileProtectionNone} ofItemAtPath:path error:nil];
// Set Realm configuration
RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration];
NSURL *databaseURL = [[NSURL fileURLWithPath:path] URLByAppendingPathComponent:kTalkDatabaseFileName];
configuration.fileURL = databaseURL;
configuration.schemaVersion = kTalkDatabaseSchemaVersion;
configuration.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
// At the very minimum we need to update the version with an empty block to indicate that the schema has been upgraded (automatically) by Realm
};
// Tell Realm to use this new configuration object for the default Realm
[RLMRealmConfiguration setDefaultConfiguration:configuration];
// Now that we've told Realm how to handle the schema change, opening the file
// will automatically perform the migration
[RLMRealm defaultRealm];
#ifdef DEBUG
// Copy Talk DB to Documents directory
NSString *dbCopyPath = [NSString stringWithFormat:@"%@/%@", NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0], kTalkDatabaseFileName];
NSURL *dbCopyURL = [NSURL fileURLWithPath:dbCopyPath];
[[NSFileManager defaultManager] removeItemAtURL:dbCopyURL error:nil];
[[NSFileManager defaultManager] copyItemAtURL:databaseURL toURL:dbCopyURL error:nil];
#endif
}
return self;
}
#pragma mark - Talk accounts
- (NSInteger)numberOfAccounts
{
return [TalkAccount allObjects].count;
}
- (TalkAccount *)activeAccount
{
TalkAccount *managedActiveAccount = [TalkAccount objectsWhere:(@"active = true")].firstObject;
if (managedActiveAccount) {
return [[TalkAccount alloc] initWithValue:managedActiveAccount];
}
return nil;
}
- (NSArray *)allAccounts
{
NSMutableArray *allAccounts = [NSMutableArray new];
for (TalkAccount *managedAccount in [TalkAccount allObjects]) {
TalkAccount *account = [[TalkAccount alloc] initWithValue:managedAccount];
[allAccounts addObject:account];
}
return allAccounts;
}
- (NSArray *)inactiveAccounts
{
NSMutableArray *inactiveAccounts = [NSMutableArray new];
for (TalkAccount *managedInactiveAccount in [TalkAccount objectsWhere:(@"active = false")]) {
TalkAccount *inactiveAccount = [[TalkAccount alloc] initWithValue:managedInactiveAccount];
[inactiveAccounts addObject:inactiveAccount];
}
return inactiveAccounts;
}
- (TalkAccount *)talkAccountForAccountId:(NSString *)accountId
{
NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@", accountId];
TalkAccount *managedAccount = [TalkAccount objectsWithPredicate:query].firstObject;
if (managedAccount) {
return [[TalkAccount alloc] initWithValue:managedAccount];
}
return nil;
}
- (TalkAccount *)talkAccountForUserId:(NSString *)userId inServer:(NSString *)server
{
NSPredicate *query = [NSPredicate predicateWithFormat:@"userId = %@ AND server = %@", userId, server];
TalkAccount *managedAccount = [TalkAccount objectsWithPredicate:query].firstObject;
if (managedAccount) {
return [[TalkAccount alloc] initWithValue:managedAccount];
}
return nil;
}
- (void)setActiveAccountWithAccountId:(NSString *)accountId
{
RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
for (TalkAccount *account in [TalkAccount allObjects]) {
account.active = NO;
}
NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@", accountId];
TalkAccount *activeAccount = [TalkAccount objectsWithPredicate:query].firstObject;
activeAccount.active = YES;
[realm commitWriteTransaction];
}
- (NSString *)accountIdForUser:(NSString *)user inServer:(NSString *)server
{
return [NSString stringWithFormat:@"%@@%@", user, server];
}
- (void)createAccountForUser:(NSString *)user inServer:(NSString *)server
{
TalkAccount *account = [[TalkAccount alloc] init];
NSString *accountId = [self accountIdForUser:user inServer:server];
account.accountId = accountId;
account.server = server;
account.user = user;
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[realm addObject:account];
}];
}
- (void)removeAccountWithAccountId:(NSString *)accountId
{
RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
BOOL isLastAccount = [self numberOfAccounts] == 1;
NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@", accountId];
TalkAccount *removeAccount = [TalkAccount objectsWithPredicate:query].firstObject;
if (removeAccount) {
[realm deleteObject:removeAccount];
}
ServerCapabilities *serverCapabilities = [ServerCapabilities objectsWithPredicate:query].firstObject;
if (serverCapabilities) {
[realm deleteObject:serverCapabilities];
}
[realm deleteObjects:[NCRoom objectsWithPredicate:query]];
[realm deleteObjects:[NCChatMessage objectsWithPredicate:query]];
[realm deleteObjects:[NCChatBlock objectsWithPredicate:query]];
[realm deleteObjects:[NCContact objectsWithPredicate:query]];
if (isLastAccount) {
[realm deleteObjects:[ABContact allObjects]];
}
[realm commitWriteTransaction];
}
- (void)increaseUnreadBadgeNumberForAccountId:(NSString *)accountId
{
RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@", accountId];
TalkAccount *account = [TalkAccount objectsWithPredicate:query].firstObject;
account.unreadBadgeNumber += 1;
account.unreadNotification = YES;
[realm commitWriteTransaction];
}
- (void)decreaseUnreadBadgeNumberForAccountId:(NSString *)accountId
{
RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@", accountId];
TalkAccount *account = [TalkAccount objectsWithPredicate:query].firstObject;
account.unreadBadgeNumber = (account.unreadBadgeNumber > 0) ? account.unreadBadgeNumber - 1 : 0;
account.unreadNotification = (account.unreadBadgeNumber > 0) ? account.unreadNotification : NO;
[realm commitWriteTransaction];
}
- (void)resetUnreadBadgeNumberForAccountId:(NSString *)accountId
{
RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@", accountId];
TalkAccount *account = [TalkAccount objectsWithPredicate:query].firstObject;
account.unreadBadgeNumber = 0;
account.unreadNotification = NO;
[realm commitWriteTransaction];
}
- (NSInteger)numberOfInactiveAccountsWithUnreadNotifications
{
return [TalkAccount objectsWhere:(@"active = false AND unreadNotification = true")].count;
}
- (NSInteger)numberOfUnreadNotifications
{
NSInteger unreadNotifications = 0;
for (TalkAccount *account in [TalkAccount allObjects]) {
unreadNotifications += account.unreadBadgeNumber;
}
return unreadNotifications;
}
- (void)removeUnreadNotificationForInactiveAccounts
{
RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
for (TalkAccount *account in [TalkAccount allObjects]) {
account.unreadNotification = NO;
}
[realm commitWriteTransaction];
}
#pragma mark - Server capabilities
- (ServerCapabilities *)serverCapabilitiesForAccountId:(NSString *)accountId
{
NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@", accountId];
ServerCapabilities *managedServerCapabilities = [ServerCapabilities objectsWithPredicate:query].firstObject;
if (managedServerCapabilities) {
return [[ServerCapabilities alloc] initWithValue:managedServerCapabilities];
}
return nil;
}
- (void)setServerCapabilities:(NSDictionary *)serverCapabilities forAccountId:(NSString *)accountId
{
NSDictionary *serverCaps = [serverCapabilities objectForKey:@"capabilities"];
NSDictionary *version = [serverCapabilities objectForKey:@"version"];
NSDictionary *themingCaps = [serverCaps objectForKey:@"theming"];
NSDictionary *talkCaps = [serverCaps objectForKey:@"spreed"];
NSDictionary *userStatusCaps = [serverCaps objectForKey:@"user_status"];
NSDictionary *provisioningAPICaps = [serverCaps objectForKey:@"provisioning_api"];
ServerCapabilities *capabilities = [[ServerCapabilities alloc] init];
capabilities.accountId = accountId;
capabilities.name = [themingCaps objectForKey:@"name"];
capabilities.slogan = [themingCaps objectForKey:@"slogan"];
capabilities.url = [themingCaps objectForKey:@"url"];
capabilities.logo = [themingCaps objectForKey:@"logo"];
capabilities.color = [themingCaps objectForKey:@"color"];
capabilities.colorElement = [themingCaps objectForKey:@"color-element"];
capabilities.colorElementBright = [themingCaps objectForKey:@"color-element-bright"];
capabilities.colorElementDark = [themingCaps objectForKey:@"color-element-dark"];
capabilities.colorText = [themingCaps objectForKey:@"color-text"];
capabilities.background = [themingCaps objectForKey:@"background"];
capabilities.backgroundDefault = [[themingCaps objectForKey:@"background-default"] boolValue];
capabilities.backgroundPlain = [[themingCaps objectForKey:@"background-plain"] boolValue];
capabilities.version = [version objectForKey:@"string"];
capabilities.versionMajor = [[version objectForKey:@"major"] integerValue];
capabilities.versionMinor = [[version objectForKey:@"minor"] integerValue];
capabilities.versionMicro = [[version objectForKey:@"micro"] integerValue];
capabilities.edition = [version objectForKey:@"edition"];
capabilities.userStatus = [[userStatusCaps objectForKey:@"enabled"] boolValue];
capabilities.webDAVRoot = [[serverCaps objectForKey:@"core"] objectForKey:@"webdav-root"];
capabilities.extendedSupport = [[version objectForKey:@"extendedSupport"] boolValue];
capabilities.talkCapabilities = [talkCaps objectForKey:@"features"];
capabilities.chatMaxLength = [[[[talkCaps objectForKey:@"config"] objectForKey:@"chat"] objectForKey:@"max-length"] integerValue];
if ([[[[talkCaps objectForKey:@"config"] objectForKey:@"conversations"] allKeys] containsObject:@"can-create"]) {
capabilities.canCreate = [[[[talkCaps objectForKey:@"config"] objectForKey:@"conversations"] objectForKey:@"can-create"] boolValue];
} else {
capabilities.canCreate = YES;
}
capabilities.attachmentsAllowed = [[[[talkCaps objectForKey:@"config"] objectForKey:@"attachments"] objectForKey:@"allowed"] boolValue];
capabilities.attachmentsFolder = [[[talkCaps objectForKey:@"config"] objectForKey:@"attachments"] objectForKey:@"folder"];
capabilities.readStatusPrivacy = [[[[talkCaps objectForKey:@"config"] objectForKey:@"chat"] objectForKey:@"read-privacy"] boolValue];
capabilities.accountPropertyScopesVersion2 = [[provisioningAPICaps objectForKey:@"AccountPropertyScopesVersion"] integerValue] == 2;
capabilities.accountPropertyScopesFederationEnabled = [[provisioningAPICaps objectForKey:@"AccountPropertyScopesFederationEnabled"] boolValue];
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[realm addOrUpdateObject:capabilities];
}];
}
- (BOOL)serverHasTalkCapability:(NSString *)capability
{
TalkAccount *activeAccount = [self activeAccount];
return [self serverHasTalkCapability:capability forAccountId:activeAccount.accountId];
}
- (BOOL)serverHasTalkCapability:(NSString *)capability forAccountId:(NSString *)accountId
{
ServerCapabilities *serverCapabilities = [self serverCapabilitiesForAccountId:accountId];
if (serverCapabilities) {
NSArray *talkFeatures = [serverCapabilities.talkCapabilities valueForKey:@"self"];
if ([talkFeatures containsObject:capability]) {
return YES;
}
}
return NO;
}
@end