talk-ios/NextcloudTalk/NCDatabaseManager.m

812 строки
36 KiB
Objective-C

/**
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#import "NCDatabaseManager.h"
#import "ABContact.h"
#import "NCAppBranding.h"
#import "NCChatBlock.h"
#import "NCChatMessage.h"
#import "NCContact.h"
#import "NCRoom.h"
#import "NextcloudTalk-Swift.h"
NSString *const kTalkDatabaseFolder = @"Library/Application Support/Talk";
NSString *const kTalkDatabaseFileName = @"talk.realm";
uint64_t const kTalkDatabaseSchemaVersion = 75;
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 kCapabilityReadOnlyRooms = @"read-only-rooms";
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 kCapabilitySIPSupportNoPIN = @"sip-support-nopin";
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 kCapabilityRichObjectListMedia = @"rich-object-list-media";
NSString * const kCapabilityRichObjectDelete = @"rich-object-delete";
NSString * const kCapabilityUnifiedSearch = @"unified-search";
NSString * const kCapabilityChatPermission = @"chat-permission";
NSString * const kCapabilityMessageExpiration = @"message-expiration";
NSString * const kCapabilitySilentSend = @"silent-send";
NSString * const kCapabilitySilentCall = @"silent-call";
NSString * const kCapabilitySendCallNotification = @"send-call-notification";
NSString * const kCapabilityTalkPolls = @"talk-polls";
NSString * const kCapabilityRaiseHand = @"raise-hand";
NSString * const kCapabilityRecordingV1 = @"recording-v1";
NSString * const kCapabilitySingleConvStatus = @"single-conversation-status";
NSString * const kCapabilityChatKeepNotifications = @"chat-keep-notifications";
NSString * const kCapabilityConversationAvatars = @"avatar";
NSString * const kCapabilityTypingIndicators = @"typing-privacy";
NSString * const kCapabilityPublishingPermissions = @"publishing-permissions";
NSString * const kCapabilityRemindMeLater = @"remind-me-later";
NSString * const kCapabilityMarkdownMessages = @"markdown-messages";
NSString * const kCapabilityNoteToSelf = @"note-to-self";
NSString * const kCapabilityMediaCaption = @"media-caption";
NSString * const kCapabilityEditMessages = @"edit-messages";
NSString * const kCapabilityDeleteMessagesUnlimited = @"delete-messages-unlimited";
NSString * const kCapabilityFederationV1 = @"federation-v1";
NSString * const kCapabilityFederationV2 = @"federation-v2";
NSString * const kCapabilityChatReadLast = @"chat-read-last";
NSString * const kCapabilityBanV1 = @"ban-v1";
NSString * const kCapabilityMentionPermissions = @"mention-permissions";
NSString * const kCapabilityEditMessagesNoteToSelf = @"edit-messages-note-to-self";
NSString * const kCapabilityChatSummary = @"chat-summary-api";
NSString * const kCapabilityArchivedConversationsV2 = @"archived-conversations-v2";
NSString * const kCapabilityCallNotificationState = @"call-notification-state-api";
NSString * const kCapabilityForceMute = @"force-mute";
NSString * const kCapabilityTalkPollsDrafts = @"talk-polls-drafts";
NSString * const kNotificationsCapabilityExists = @"exists";
NSString * const kNotificationsCapabilityTestPush = @"test-push";
NSString * const kMinimumRequiredTalkCapability = kCapabilityForceMute; // Talk 9.0 is the minimum required version
NSString * const NCDatabaseManagerPendingFederationInvitationsDidChange = @"NCDatabaseManagerPendingFederationInvitationsDidChange";
NSString * const NCDatabaseManagerRoomCapabilitiesChangedNotification = @"NCDatabaseManagerRoomCapabilitiesChangedNotification";
@implementation NCTranslation
@end
@interface NCDatabaseManager ()
@property (nonatomic, strong) NSCache<NSString *, ServerCapabilities*> *capabilitiesCache;
@end
@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.objectClasses = @[
TalkAccount.class, NCRoom.class, ServerCapabilities.class, FederatedCapabilities.class,
NCChatMessage.class, NCChatBlock.class, NCContact.class, ABContact.class
];
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
self.capabilitiesCache = [[NSCache alloc] init];
}
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];
[_capabilitiesCache removeObjectForKey:accountId];
}
[realm deleteObjects:[NCRoom objectsWithPredicate:query]];
[realm deleteObjects:[NCChatMessage objectsWithPredicate:query]];
[realm deleteObjects:[NCChatBlock objectsWithPredicate:query]];
[realm deleteObjects:[NCContact objectsWithPredicate:query]];
[realm deleteObjects:[FederatedCapabilities 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
{
BGTaskHelper *bgTask = [BGTaskHelper startBackgroundTaskWithName:@"resetUnreadBadgeNumberForAccountId" expirationHandler:nil];
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];
[bgTask stopBackgroundTask];
}
- (NSInteger)numberOfInactiveAccountsWithUnreadNotifications
{
return [TalkAccount objectsWhere:(@"active = false AND unreadNotification = true")].count;
}
- (NSInteger)numberOfUnreadNotifications
{
// Make sure that the data on this thread is up to date.
// Failing to do so might result in inaccurate badge numbers when they were updated on a different thread
[[RLMRealm defaultRealm] refresh];
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];
}
- (void)updateTalkConfigurationHashForAccountId:(NSString *)accountId withHash:(nonnull NSString *)hash
{
BGTaskHelper *bgTask = [BGTaskHelper startBackgroundTaskWithName:@"updateTalkConfigurationHashForAccountId" expirationHandler:nil];
RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@", accountId];
TalkAccount *account = [TalkAccount objectsWithPredicate:query].firstObject;
account.lastReceivedConfigurationHash = hash;
[realm commitWriteTransaction];
[bgTask stopBackgroundTask];
}
- (void)updateLastModifiedSinceForAccountId:(NSString *)accountId with:(nonnull NSString *)modifiedSince
{
BGTaskHelper *bgTask = [BGTaskHelper startBackgroundTaskWithName:@"updateLastModifiedSinceForAccountId" expirationHandler:nil];
RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@", accountId];
TalkAccount *account = [TalkAccount objectsWithPredicate:query].firstObject;
account.lastReceivedModifiedSince = modifiedSince;
[realm commitWriteTransaction];
[bgTask stopBackgroundTask];
}
#pragma mark - Rooms
- (NCRoom *)roomWithToken:(NSString *)token forAccountId:(NSString *)accountId
{
NCRoom *unmanagedRoom = nil;
NSPredicate *query = [NSPredicate predicateWithFormat:@"token = %@ AND accountId = %@", token, accountId];
NCRoom *managedRoom = [NCRoom objectsWithPredicate:query].firstObject;
if (managedRoom) {
unmanagedRoom = [[NCRoom alloc] initWithValue:managedRoom];
}
return unmanagedRoom;
}
#pragma mark - Talk capabilities
- (void)setTalkCapabilities:(NSDictionary *)capabilitiesDict onTalkCapabilitiesObject:(TalkCapabilities *)capabilities
{
capabilities.talkCapabilities = [capabilitiesDict objectForKey:@"features"];
capabilities.hasTranslationProviders = [[[[capabilitiesDict objectForKey:@"config"] objectForKey:@"chat"] objectForKey:@"has-translation-providers"] boolValue];
capabilities.attachmentsAllowed = [[[[capabilitiesDict objectForKey:@"config"] objectForKey:@"attachments"] objectForKey:@"allowed"] boolValue];
capabilities.attachmentsFolder = [[[capabilitiesDict objectForKey:@"config"] objectForKey:@"attachments"] objectForKey:@"folder"];
capabilities.talkVersion = [capabilitiesDict objectForKey:@"version"];
NSDictionary *talkConfig = [capabilitiesDict objectForKey:@"config"];
// Call capabilities
NSDictionary *callConfig = [talkConfig objectForKey:@"call"];
NSArray *callConfigKeys = [callConfig allKeys];
if ([callConfigKeys containsObject:@"enabled"]) {
capabilities.callEnabled = [[callConfig objectForKey:@"enabled"] boolValue];
} else {
capabilities.callEnabled = YES;
}
if ([callConfigKeys containsObject:@"recording"]) {
capabilities.recordingEnabled = [[callConfig objectForKey:@"recording"] boolValue];
} else {
capabilities.recordingEnabled = NO;
}
if ([callConfigKeys containsObject:@"supported-reactions"]) {
capabilities.callReactions = [callConfig objectForKey:@"supported-reactions"];
} else {
capabilities.callReactions = (RLMArray<RLMString> *)@[];
}
// Conversations capabilities
NSDictionary *conversationsConfig = [talkConfig objectForKey:@"conversations"];
NSArray *conversationsConfigKeys = [conversationsConfig allKeys];
if ([conversationsConfigKeys containsObject:@"can-create"]) {
capabilities.canCreate = [[conversationsConfig objectForKey:@"can-create"] boolValue];
} else {
capabilities.canCreate = YES;
}
if ([conversationsConfigKeys containsObject:@"description-length"]) {
capabilities.descriptionLength = [[conversationsConfig objectForKey:@"description-length"] integerValue];
} else {
capabilities.descriptionLength = 500;
}
// Chat capabilities
NSDictionary *chatConfig = [talkConfig objectForKey:@"chat"];
NSArray *chatConfigKeys = [chatConfig allKeys];
capabilities.readStatusPrivacy = [[chatConfig objectForKey:@"read-privacy"] boolValue];
capabilities.chatMaxLength = [[chatConfig objectForKey:@"max-length"] integerValue];
if ([chatConfigKeys containsObject:@"typing-privacy"]) {
capabilities.typingPrivacy = [[chatConfig objectForKey:@"typing-privacy"] boolValue];
} else {
capabilities.typingPrivacy = YES;
}
if ([chatConfigKeys containsObject:@"summary-threshold"]) {
capabilities.summaryThreshold = [[chatConfig objectForKey:@"summary-threshold"] intValue];
} else {
capabilities.summaryThreshold = 0;
}
// Translations
id translations = [[[capabilitiesDict objectForKey:@"config"] objectForKey:@"chat"] objectForKey:@"translations"];
if ([translations isKindOfClass:[NSArray class]]) {
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:translations
options:0
error:&error];
if (jsonData) {
capabilities.translations = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
} else {
NSLog(@"Error generating translations JSON string: %@", error);
}
}
// Federation capabilities
NSDictionary *federationConfig = [talkConfig objectForKey:@"federation"];
NSArray *federationConfigKeys = [federationConfig allKeys];
if ([federationConfigKeys containsObject:@"enabled"]) {
capabilities.federationEnabled = [[federationConfig objectForKey:@"enabled"] boolValue];
} else {
capabilities.federationEnabled = NO;
}
if ([federationConfigKeys containsObject:@"incoming-enabled"]) {
capabilities.federationIncomingEnabled = [[federationConfig objectForKey:@"incoming-enabled"] boolValue];
} else {
capabilities.federationIncomingEnabled = NO;
}
if ([federationConfigKeys containsObject:@"outgoing-enabled"]) {
capabilities.federationOutgoingEnabled = [[federationConfig objectForKey:@"outgoing-enabled"] boolValue];
} else {
capabilities.federationOutgoingEnabled = NO;
}
if ([federationConfigKeys containsObject:@"only-trusted-servers"]) {
capabilities.federationOnlyTrustedServers = [[federationConfig objectForKey:@"only-trusted-servers"] boolValue];
} else {
capabilities.federationOnlyTrustedServers = NO;
}
NSDictionary *previewConfig = [talkConfig objectForKey:@"previews"];
NSArray *previewConfigKeys = [previewConfig allKeys];
if ([previewConfigKeys containsObject:@"max-gif-size"]) {
capabilities.maxGifSize = [[previewConfig objectForKey:@"max-gif-size"] intValue];
}
}
#pragma mark - Federated capabilities
- (FederatedCapabilities * __nullable)federatedCapabilitiesForAccountId:(NSString *)accountId remoteServer:(NSString *)remoteServer roomToken:(NSString *)roomToken
{
NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@ AND remoteServer = %@ AND roomToken = %@", accountId, remoteServer, roomToken];
FederatedCapabilities *managedFederatedCapabilities = [FederatedCapabilities objectsWithPredicate:query].firstObject;
if (managedFederatedCapabilities) {
FederatedCapabilities *unmanagedFederatedCapabilities = [[FederatedCapabilities alloc] initWithValue:managedFederatedCapabilities];
return unmanagedFederatedCapabilities;
}
return nil;
}
- (void)setFederatedCapabilities:(NSDictionary *)federatedCapabilitiesDict forAccountId:(NSString *)accountId remoteServer:(NSString *)remoteServer roomToken:(NSString *)roomToken withProxyHash:(NSString *)proxyHash
{
FederatedCapabilities *federatedCapabilities = [[FederatedCapabilities alloc] init];
federatedCapabilities.internalId = [NSString stringWithFormat:@"%@@%@@%@", accountId, remoteServer, roomToken];
federatedCapabilities.accountId = accountId;
federatedCapabilities.remoteServer = remoteServer;
federatedCapabilities.roomToken = roomToken;
[self setTalkCapabilities:federatedCapabilitiesDict onTalkCapabilitiesObject:federatedCapabilities];
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[realm addOrUpdateObject:federatedCapabilities];
// Update the hash
NSPredicate *query = [NSPredicate predicateWithFormat:@"token = %@ AND accountId = %@", roomToken, accountId];
NCRoom *managedRoom = [NCRoom objectsWithPredicate:query].firstObject;
if (managedRoom) {
managedRoom.lastReceivedProxyHash = proxyHash;
}
NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] init];
[userInfo setObject:accountId forKey:@"accountId"];
[userInfo setObject:roomToken forKey:@"roomToken"];
[[NSNotificationCenter defaultCenter] postNotificationName:NCDatabaseManagerRoomCapabilitiesChangedNotification
object:self
userInfo:userInfo];
}];
}
#pragma mark - Room capabilities
- (BOOL)roomHasTalkCapability:(NSString *)capability forRoom:(NCRoom *)room
{
if (!room.isFederated) {
return [self serverHasTalkCapability:capability forAccountId:room.accountId];
}
FederatedCapabilities *federatedCapabilities = [self federatedCapabilitiesForAccountId:room.accountId remoteServer:room.remoteServer roomToken:room.token];
if (!federatedCapabilities) {
return NO;
}
NSArray *talkFeatures = [federatedCapabilities.talkCapabilities valueForKey:@"self"];
return [talkFeatures containsObject:capability];
}
- (TalkCapabilities * __nullable)roomTalkCapabilitiesForRoom:(NCRoom *)room
{
if (room.isFederated) {
FederatedCapabilities *federatedCapabilities = [self federatedCapabilitiesForAccountId:room.accountId remoteServer:room.remoteServer roomToken:room.token];
if (federatedCapabilities) {
TalkCapabilities *unmanagedTalkCapabilities = [[TalkCapabilities alloc] initWithValue:federatedCapabilities];
return unmanagedTalkCapabilities;
}
return nil;
}
ServerCapabilities *serverCapabilities = [self serverCapabilitiesForAccountId:room.accountId];
if (serverCapabilities) {
TalkCapabilities *unmanagedTalkCapabilities = [[TalkCapabilities alloc] initWithValue:serverCapabilities];
return unmanagedTalkCapabilities;
}
return nil;
}
#pragma mark - Server capabilities
- (ServerCapabilities *)serverCapabilities {
TalkAccount *activeAccount = [self activeAccount];
return [self serverCapabilitiesForAccountId:activeAccount.accountId];
}
- (ServerCapabilities *)serverCapabilitiesForAccountId:(NSString *)accountId
{
ServerCapabilities *cachedCapabilities = [self.capabilitiesCache objectForKey:accountId];
if (cachedCapabilities) {
return cachedCapabilities;
}
NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@", accountId];
ServerCapabilities *managedServerCapabilities = [ServerCapabilities objectsWithPredicate:query].firstObject;
if (managedServerCapabilities) {
ServerCapabilities *unmanagedServerCapabilities = [[ServerCapabilities alloc] initWithValue:managedServerCapabilities];
[self.capabilitiesCache setObject:unmanagedServerCapabilities forKey:accountId];
return unmanagedServerCapabilities;
}
return nil;
}
- (void)setServerCapabilities:(NSDictionary *)serverCapabilities forAccountId:(NSString *)accountId
{
NSDictionary *serverCaps = [serverCapabilities objectForKey:@"capabilities"];
NSDictionary *coreCaps = [serverCaps objectForKey:@"core"];
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"];
NSDictionary *guestsCaps = [serverCaps objectForKey:@"guests"];
NSDictionary *notificationsCaps = [serverCaps objectForKey:@"notifications"];
NSDictionary *davCaps = [serverCaps objectForKey:@"dav"];
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.extendedSupport = [[version objectForKey:@"extendedSupport"] boolValue];
capabilities.accountPropertyScopesVersion2 = [[provisioningAPICaps objectForKey:@"AccountPropertyScopesVersion"] integerValue] == 2;
capabilities.accountPropertyScopesFederationEnabled = [[provisioningAPICaps objectForKey:@"AccountPropertyScopesFederationEnabled"] boolValue];
capabilities.accountPropertyScopesFederatedEnabled = [[provisioningAPICaps objectForKey:@"AccountPropertyScopesFederatedEnabled"] boolValue];
capabilities.accountPropertyScopesPublishedEnabled = [[provisioningAPICaps objectForKey:@"AccountPropertyScopesPublishedEnabled"] boolValue];
capabilities.guestsAppEnabled = [[guestsCaps objectForKey:@"enabled"] boolValue];
capabilities.referenceApiSupported = [[coreCaps objectForKey:@"reference-api"] boolValue];
capabilities.modRewriteWorking = [[coreCaps objectForKey:@"mod-rewrite-working"] boolValue];
capabilities.absenceSupported = [[davCaps objectForKey:@"absence-supported"] boolValue];
capabilities.absenceReplacementSupported = [[davCaps objectForKey:@"absence-replacement"] boolValue];
capabilities.notificationsCapabilities = [notificationsCaps objectForKey:@"ocs-endpoints"];
[self setTalkCapabilities:talkCaps onTalkCapabilitiesObject:capabilities];
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[realm addOrUpdateObject:capabilities];
}];
ServerCapabilities *unmanagedServerCapabilities = [[ServerCapabilities alloc] initWithValue:capabilities];
[self.capabilitiesCache setObject:unmanagedServerCapabilities forKey:accountId];
}
- (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;
}
- (BOOL)serverHasNotificationsCapability:(NSString *)capability forAccountId:(NSString *)accountId
{
ServerCapabilities *serverCapabilities = [self serverCapabilitiesForAccountId:accountId];
if (serverCapabilities) {
NSArray *notificationsFeatures = [serverCapabilities.notificationsCapabilities valueForKey:@"self"];
if ([notificationsFeatures containsObject:capability]) {
return YES;
}
}
return NO;
}
- (BOOL)serverCanInviteFederatedUsersforAccountId:(NSString *)accountId
{
ServerCapabilities *serverCapabilities = [self serverCapabilitiesForAccountId:accountId];
if (serverCapabilities && [self serverHasTalkCapability:kCapabilityFederationV1 forAccountId:accountId]) {
return serverCapabilities.federationEnabled && serverCapabilities.federationOutgoingEnabled;
}
return NO;
}
- (void)setExternalSignalingServerVersion:(NSString *)version forAccountId:(NSString *)accountId
{
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@", accountId];
ServerCapabilities *managedServerCapabilities = [ServerCapabilities objectsWithPredicate:query].firstObject;
if (managedServerCapabilities && managedServerCapabilities.externalSignalingServerVersion != version) {
managedServerCapabilities.externalSignalingServerVersion = version;
ServerCapabilities *unmanagedServerCapabilities = [[ServerCapabilities alloc] initWithValue:managedServerCapabilities];
[self.capabilitiesCache setObject:unmanagedServerCapabilities forKey:accountId];
}
}];
}
- (BOOL)hasAvailableTranslationsForAccountId:(NSString *)accountId
{
return [self hasTranslationProvidersForAccountId:accountId] || [self availableTranslationsForAccountId:accountId].count > 0;
}
- (BOOL)hasTranslationProvidersForAccountId:(NSString *)accountId
{
ServerCapabilities *serverCapabilities = [self serverCapabilitiesForAccountId:accountId];
return serverCapabilities.hasTranslationProviders;
}
- (NSArray *)availableTranslationsForAccountId:(NSString *)accountId
{
ServerCapabilities *serverCapabilities = [self serverCapabilitiesForAccountId:accountId];
if (serverCapabilities) {
NSArray *translations = [self translationsArrayFromTranslationsJSONString:serverCapabilities.translations];
return [self translationsFromTranslationsArray:translations];
}
return @[];
}
- (NSArray *)translationsArrayFromTranslationsJSONString:(NSString *)translations
{
NSArray *translationsArray = @[];
NSData *data = [translations dataUsingEncoding:NSUTF8StringEncoding];
if (data) {
NSError* error;
NSArray* jsonData = [NSJSONSerialization JSONObjectWithData:data
options:0
error:&error];
if (jsonData) {
translationsArray = jsonData;
} else {
NSLog(@"Error retrieving translations JSON data: %@", error);
}
}
return translationsArray;
}
- (NSArray *)translationsFromTranslationsArray:(NSArray *)translations
{
NSMutableArray *availableTranslations = [NSMutableArray new];
for (NSDictionary *translationDict in translations) {
NCTranslation *translation = [[NCTranslation alloc] init];
translation.from = [translationDict objectForKey:@"from"];
translation.fromLabel = [translationDict objectForKey:@"fromLabel"];
translation.to = [translationDict objectForKey:@"to"];
translation.toLabel = [translationDict objectForKey:@"toLabel"];
[availableTranslations addObject:translation];
}
return availableTranslations;
}
- (void)increasePendingFederationInvitationForAccountId:(NSString *)accountId
{
RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@", accountId];
TalkAccount *account = [TalkAccount objectsWithPredicate:query].firstObject;
account.pendingFederationInvitations += 1;
[realm commitWriteTransaction];
}
- (void)decreasePendingFederationInvitationForAccountId:(NSString *)accountId
{
RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@", accountId];
TalkAccount *account = [TalkAccount objectsWithPredicate:query].firstObject;
account.pendingFederationInvitations = (account.pendingFederationInvitations > 0) ? account.pendingFederationInvitations - 1 : 0;
[realm commitWriteTransaction];
[[NSNotificationCenter defaultCenter] postNotificationName:NCDatabaseManagerPendingFederationInvitationsDidChange
object:self
userInfo:nil];
}
- (void)setPendingFederationInvitationForAccountId:(NSString *)accountId with:(NSInteger)numberOfPendingInvitations
{
RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@", accountId];
TalkAccount *account = [TalkAccount objectsWithPredicate:query].firstObject;
account.pendingFederationInvitations = numberOfPendingInvitations;
[realm commitWriteTransaction];
[[NSNotificationCenter defaultCenter] postNotificationName:NCDatabaseManagerPendingFederationInvitationsDidChange
object:self
userInfo:nil];
}
@end