Initial commit

This commit is contained in:
Tha_14
2024-02-22 21:43:11 +02:00
commit 1b96a031d2
1108 changed files with 157706 additions and 0 deletions

View File

@ -0,0 +1,112 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#import <Foundation/Foundation.h>
#import "OCTToxConstants.h"
#import "OCTManagerConstants.h"
@class OCTObject;
@class OCTFriend;
@class OCTChat;
@class OCTCall;
@class OCTMessageAbstract;
@class OCTSettingsStorageObject;
@class RLMResults;
@interface OCTRealmManager : NSObject
/**
* Storage with all objcTox settings.
*/
@property (strong, nonatomic, readonly) OCTSettingsStorageObject *settingsStorage;
/**
* Migrate unencrypted database to encrypted one.
*
* @param databasePath Path to unencrypted database.
* @param encryptionKey Key used to encrypt database.
* @param error Error parameter will be filled in case of failure. It will contain RLMRealm or NSFileManager error.
*
* @return YES on success, NO on failure.
*/
+ (BOOL)migrateToEncryptedDatabase:(NSString *)databasePath
encryptionKey:(NSData *)encryptionKey
error:(NSError **)error;
/**
* Create RealmManager.
*
* @param fileURL path to Realm file. File will be created if it doesn't exist.
* @param encryptionKey A 64-byte key to use to encrypt the data, or nil if encryption is not enabled.
*/
- (instancetype)initWithDatabaseFileURL:(NSURL *)fileURL encryptionKey:(NSData *)encryptionKey;
- (NSURL *)realmFileURL;
#pragma mark - Basic methods
- (id)objectWithUniqueIdentifier:(NSString *)uniqueIdentifier class:(Class)class;
- (RLMResults *)objectsWithClass:(Class)class predicate:(NSPredicate *)predicate;
- (void)addObject:(OCTObject *)object;
- (void)deleteObject:(OCTObject *)object;
/*
* All realm objects should be updated ONLY using following two methods.
*
* Specified object will be passed in block.
*/
- (void)updateObject:(OCTObject *)object withBlock:(void (^)(id theObject))updateBlock;
- (void)updateObjectsWithClass:(Class)class
predicate:(NSPredicate *)predicate
updateBlock:(void (^)(id theObject))updateBlock;
#pragma mark - Other methods
- (OCTFriend *)friendWithPublicKey:(NSString *)publicKey;
- (OCTChat *)getOrCreateChatWithFriend:(OCTFriend *)friend;
- (OCTCall *)createCallWithChat:(OCTChat *)chat status:(OCTCallStatus)status;
/**
* Gets the current call for the chat if and only if it exists.
* This will not create a call object.
* @param chat The chat that is related to the call.
* @return A call object if it exists, nil if no call is session for this call.
*/
- (OCTCall *)getCurrentCallForChat:(OCTChat *)chat;
- (void)removeMessages:(NSArray<OCTMessageAbstract *> *)messages;
- (void)removeAllMessagesInChat:(OCTChat *)chat removeChat:(BOOL)removeChat;
/**
* Converts all the OCTCalls to OCTMessageCalls.
* Only use this when first starting the app or during termination.
*/
- (void)convertAllCallsToMessages;
- (OCTMessageAbstract *)addMessageWithText:(NSString *)text
type:(OCTToxMessageType)type
chat:(OCTChat *)chat
sender:(OCTFriend *)sender
messageId:(OCTToxMessageId)messageId
msgv3HashHex:(NSString *)msgv3HashHex
sentPush:(BOOL)sentPush
tssent:(UInt32)tssent
tsrcvd:(UInt32)tsrcvd;
- (OCTMessageAbstract *)addMessageWithFileNumber:(OCTToxFileNumber)fileNumber
fileType:(OCTMessageFileType)fileType
fileSize:(OCTToxFileSize)fileSize
fileName:(NSString *)fileName
filePath:(NSString *)filePath
fileUTI:(NSString *)fileUTI
chat:(OCTChat *)chat
sender:(OCTFriend *)sender;
- (OCTMessageAbstract *)addMessageCall:(OCTCall *)call;
@end

View File

@ -0,0 +1,665 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#import <Realm/Realm.h>
#import "OCTRealmManager.h"
#import "OCTFriend.h"
#import "OCTFriendRequest.h"
#import "OCTChat.h"
#import "OCTCall.h"
#import "OCTMessageAbstract.h"
#import "OCTMessageText.h"
#import "OCTMessageFile.h"
#import "OCTMessageCall.h"
#import "OCTSettingsStorageObject.h"
#import "OCTLogging.h"
static const uint64_t kCurrentSchemeVersion = 16;
static NSString *kSettingsStorageObjectPrimaryKey = @"kSettingsStorageObjectPrimaryKey";
@interface OCTRealmManager ()
@property (strong, nonatomic) dispatch_queue_t queue;
@property (strong, nonatomic) RLMRealm *realm;
@end
@implementation OCTRealmManager
@synthesize settingsStorage = _settingsStorage;
#pragma mark - Class methods
+ (BOOL)migrateToEncryptedDatabase:(NSString *)databasePath
encryptionKey:(NSData *)encryptionKey
error:(NSError **)error
{
NSString *tempPath = [databasePath stringByAppendingPathExtension:@"tmp"];
@autoreleasepool {
RLMRealm *old = [OCTRealmManager createRealmWithFileURL:[NSURL fileURLWithPath:databasePath]
encryptionKey:nil
error:error];
if (! old) {
return NO;
}
if (! [old writeCopyToURL:[NSURL fileURLWithPath:tempPath] encryptionKey:encryptionKey error:error]) {
return NO;
}
}
if (! [[NSFileManager defaultManager] removeItemAtPath:databasePath error:error]) {
return NO;
}
if (! [[NSFileManager defaultManager] moveItemAtPath:tempPath toPath:databasePath error:error]) {
return NO;
}
return YES;
}
#pragma mark - Lifecycle
- (instancetype)initWithDatabaseFileURL:(NSURL *)fileURL encryptionKey:(NSData *)encryptionKey
{
NSParameterAssert(fileURL);
self = [super init];
if (! self) {
return nil;
}
OCTLogInfo(@"init with fileURL %@", fileURL);
_queue = dispatch_queue_create("OCTRealmManager queue", NULL);
__weak OCTRealmManager *weakSelf = self;
dispatch_sync(_queue, ^{
__strong OCTRealmManager *strongSelf = weakSelf;
// TODO handle error
self->_realm = [OCTRealmManager createRealmWithFileURL:fileURL encryptionKey:encryptionKey error:nil];
[strongSelf createSettingsStorage];
});
[self convertAllCallsToMessages];
return self;
}
#pragma mark - Public
- (NSURL *)realmFileURL
{
return self.realm.configuration.fileURL;
}
#pragma mark - Basic methods
- (id)objectWithUniqueIdentifier:(NSString *)uniqueIdentifier class:(Class)class
{
NSParameterAssert(uniqueIdentifier);
NSParameterAssert(class);
__block OCTObject *object = nil;
dispatch_sync(self.queue, ^{
object = [class objectInRealm:self.realm forPrimaryKey:uniqueIdentifier];
});
return object;
}
- (RLMResults *)objectsWithClass:(Class)class predicate:(NSPredicate *)predicate
{
NSParameterAssert(class);
__block RLMResults *results;
dispatch_sync(self.queue, ^{
results = [class objectsInRealm:self.realm withPredicate:predicate];
});
return results;
}
- (void)updateObject:(OCTObject *)object withBlock:(void (^)(id theObject))updateBlock
{
NSParameterAssert(object);
NSParameterAssert(updateBlock);
// OCTLogInfo(@"updateObject %@", object);
dispatch_sync(self.queue, ^{
[self.realm beginWriteTransaction];
updateBlock(object);
[self.realm commitWriteTransaction];
});
}
- (void)updateObjectsWithClass:(Class)class
predicate:(NSPredicate *)predicate
updateBlock:(void (^)(id theObject))updateBlock
{
NSParameterAssert(class);
NSParameterAssert(updateBlock);
// OCTLogInfo(@"updating objects of class %@ with predicate %@", NSStringFromClass(class), predicate);
dispatch_sync(self.queue, ^{
RLMResults *results = [class objectsInRealm:self.realm withPredicate:predicate];
[self.realm beginWriteTransaction];
for (id object in results) {
updateBlock(object);
}
[self.realm commitWriteTransaction];
});
}
- (void)addObject:(OCTObject *)object
{
NSParameterAssert(object);
// OCTLogInfo(@"add object %@", object);
dispatch_sync(self.queue, ^{
[self.realm beginWriteTransaction];
[self.realm addObject:object];
[self.realm commitWriteTransaction];
});
}
- (void)deleteObject:(OCTObject *)object
{
NSParameterAssert(object);
// OCTLogInfo(@"delete object %@", object);
dispatch_sync(self.queue, ^{
[self.realm beginWriteTransaction];
[self.realm deleteObject:object];
[self.realm commitWriteTransaction];
});
}
#pragma mark - Other methods
+ (RLMRealm *)createRealmWithFileURL:(NSURL *)fileURL encryptionKey:(NSData *)encryptionKey error:(NSError **)error
{
RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration];
configuration.fileURL = fileURL;
configuration.schemaVersion = kCurrentSchemeVersion;
configuration.migrationBlock = [self realmMigrationBlock];
configuration.encryptionKey = encryptionKey;
RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:error];
if (! realm && error) {
OCTLogInfo(@"Cannot create Realm, error %@", *error);
}
return realm;
}
- (void)createSettingsStorage
{
_settingsStorage = [OCTSettingsStorageObject objectInRealm:self.realm
forPrimaryKey:kSettingsStorageObjectPrimaryKey];
if (! _settingsStorage) {
OCTLogInfo(@"no _settingsStorage, creating it");
_settingsStorage = [OCTSettingsStorageObject new];
_settingsStorage.uniqueIdentifier = kSettingsStorageObjectPrimaryKey;
[self.realm beginWriteTransaction];
[self.realm addObject:_settingsStorage];
[self.realm commitWriteTransaction];
}
}
- (OCTFriend *)friendWithPublicKey:(NSString *)publicKey
{
NSAssert(publicKey, @"Public key should be non-empty.");
__block OCTFriend *friend;
dispatch_sync(self.queue, ^{
friend = [[OCTFriend objectsInRealm:self.realm where:@"publicKey == %@", publicKey] firstObject];
});
return friend;
}
- (OCTChat *)getOrCreateChatWithFriend:(OCTFriend *)friend
{
__block OCTChat *chat = nil;
dispatch_sync(self.queue, ^{
// TODO add this (friends.@count == 1) condition. Currentry Realm doesn't support collection queries
// See https://github.com/realm/realm-cocoa/issues/1490
chat = [[OCTChat objectsInRealm:self.realm where:@"ANY friends == %@", friend] firstObject];
if (chat) {
return;
}
OCTLogInfo(@"creating chat with friend %@", friend);
chat = [OCTChat new];
chat.lastActivityDateInterval = [[NSDate date] timeIntervalSince1970];
[self.realm beginWriteTransaction];
[self.realm addObject:chat];
[chat.friends addObject:friend];
[self.realm commitWriteTransaction];
});
return chat;
}
- (OCTCall *)createCallWithChat:(OCTChat *)chat status:(OCTCallStatus)status
{
__block OCTCall *call = nil;
dispatch_sync(self.queue, ^{
call = [[OCTCall objectsInRealm:self.realm where:@"chat == %@", chat] firstObject];
if (call) {
return;
}
OCTLogInfo(@"creating call with chat %@", chat);
call = [OCTCall new];
call.status = status;
call.chat = chat;
[self.realm beginWriteTransaction];
[self.realm addObject:call];
[self.realm commitWriteTransaction];
});
return call;
}
- (OCTCall *)getCurrentCallForChat:(OCTChat *)chat
{
__block OCTCall *call = nil;
dispatch_sync(self.queue, ^{
call = [[OCTCall objectsInRealm:self.realm where:@"chat == %@", chat] firstObject];
});
return call;
}
- (void)removeMessages:(NSArray<OCTMessageAbstract *> *)messages
{
NSParameterAssert(messages);
OCTLogInfo(@"removing messages %lu", (unsigned long)messages.count);
dispatch_sync(self.queue, ^{
[self.realm beginWriteTransaction];
NSMutableSet *changedChats = [NSMutableSet new];
for (OCTMessageAbstract *message in messages) {
[changedChats addObject:message.chatUniqueIdentifier];
}
[self removeMessagesWithSubmessages:messages];
for (NSString *chatUniqueIdentifier in changedChats) {
RLMResults *messages = [OCTMessageAbstract objectsInRealm:self.realm where:@"chatUniqueIdentifier == %@", chatUniqueIdentifier];
messages = [messages sortedResultsUsingKeyPath:@"dateInterval" ascending:YES];
OCTChat *chat = [OCTChat objectInRealm:self.realm forPrimaryKey:chatUniqueIdentifier];
chat.lastMessage = messages.lastObject;
}
[self.realm commitWriteTransaction];
});
}
- (void)removeAllMessagesInChat:(OCTChat *)chat removeChat:(BOOL)removeChat
{
NSParameterAssert(chat);
OCTLogInfo(@"removing chat with all messages %@", chat);
dispatch_sync(self.queue, ^{
RLMResults *messages = [OCTMessageAbstract objectsInRealm:self.realm where:@"chatUniqueIdentifier == %@", chat.uniqueIdentifier];
[self.realm beginWriteTransaction];
[self removeMessagesWithSubmessages:messages];
if (removeChat) {
[self.realm deleteObject:chat];
}
[self.realm commitWriteTransaction];
});
}
- (void)convertAllCallsToMessages
{
RLMResults *calls = [OCTCall allObjectsInRealm:self.realm];
OCTLogInfo(@"removing %lu calls", (unsigned long)calls.count);
for (OCTCall *call in calls) {
[self addMessageCall:call];
}
[self.realm beginWriteTransaction];
[self.realm deleteObjects:calls];
[self.realm commitWriteTransaction];
}
- (OCTMessageAbstract *)addMessageWithText:(NSString *)text
type:(OCTToxMessageType)type
chat:(OCTChat *)chat
sender:(OCTFriend *)sender
messageId:(OCTToxMessageId)messageId
msgv3HashHex:(NSString *)msgv3HashHex
sentPush:(BOOL)sentPush
tssent:(UInt32)tssent
tsrcvd:(UInt32)tsrcvd
{
NSParameterAssert(text);
OCTLogInfo(@"adding messageText to chat %@", chat);
OCTMessageText *messageText = [OCTMessageText new];
messageText.text = text;
messageText.isDelivered = NO;
messageText.type = type;
messageText.messageId = messageId;
messageText.msgv3HashHex = msgv3HashHex;
messageText.sentPush = sentPush;
return [self addMessageAbstractWithChat:chat sender:sender messageText:messageText messageFile:nil messageCall:nil tssent:tssent tsrcvd:tsrcvd];
}
- (OCTMessageAbstract *)addMessageWithFileNumber:(OCTToxFileNumber)fileNumber
fileType:(OCTMessageFileType)fileType
fileSize:(OCTToxFileSize)fileSize
fileName:(NSString *)fileName
filePath:(NSString *)filePath
fileUTI:(NSString *)fileUTI
chat:(OCTChat *)chat
sender:(OCTFriend *)sender
{
OCTLogInfo(@"adding messageFile to chat %@, fileSize %lld", chat, fileSize);
OCTMessageFile *messageFile = [OCTMessageFile new];
messageFile.internalFileNumber = fileNumber;
messageFile.fileType = fileType;
messageFile.fileSize = fileSize;
messageFile.fileName = fileName;
[messageFile internalSetFilePath:filePath];
messageFile.fileUTI = fileUTI;
return [self addMessageAbstractWithChat:chat sender:sender messageText:nil messageFile:messageFile messageCall:nil tssent:0 tsrcvd:0];
}
- (OCTMessageAbstract *)addMessageCall:(OCTCall *)call
{
OCTLogInfo(@"adding messageCall to call %@", call);
OCTMessageCallEvent event;
switch (call.status) {
case OCTCallStatusDialing:
case OCTCallStatusRinging:
event = OCTMessageCallEventUnanswered;
break;
case OCTCallStatusActive:
event = OCTMessageCallEventAnswered;
break;
}
OCTMessageCall *messageCall = [OCTMessageCall new];
messageCall.callDuration = call.callDuration;
messageCall.callEvent = event;
return [self addMessageAbstractWithChat:call.chat sender:call.caller messageText:nil messageFile:nil messageCall:messageCall tssent:0 tsrcvd:0];
}
#pragma mark - Private
+ (RLMMigrationBlock)realmMigrationBlock
{
return ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
if (oldSchemaVersion < 1) {
// objcTox version 0.1.0
}
if (oldSchemaVersion < 2) {
// objcTox version 0.2.1
}
if (oldSchemaVersion < 3) {
// objcTox version 0.4.0
}
if (oldSchemaVersion < 4) {
// objcTox version 0.5.0
[self doMigrationVersion4:migration];
}
if (oldSchemaVersion < 5) {
// OCTMessageAbstract: chat property replaced with chatUniqueIdentifier
[self doMigrationVersion5:migration];
}
if (oldSchemaVersion < 6) {
// OCTSettingsStorageObject: adding genericSettingsData property.
}
if (oldSchemaVersion < 7) {
[self doMigrationVersion7:migration];
}
if (oldSchemaVersion < 8) {
[self doMigrationVersion8:migration];
}
if (oldSchemaVersion < 9) {}
if (oldSchemaVersion < 10) {}
if (oldSchemaVersion < 11) {
[self doMigrationVersion11:migration];
}
if (oldSchemaVersion < 12) {
[self doMigrationVersion12:migration];
}
if (oldSchemaVersion < 13) {
[self doMigrationVersion13:migration];
}
if (oldSchemaVersion < 14) {
[self doMigrationVersion14:migration];
}
if (oldSchemaVersion < 16) {
[self doMigrationVersion16:migration];
}
};
}
+ (void)doMigrationVersion4:(RLMMigration *)migration
{
[migration enumerateObjects:OCTChat.className block:^(RLMObject *oldObject, RLMObject *newObject) {
newObject[@"enteredText"] = [oldObject[@"enteredText"] length] > 0 ? oldObject[@"enteredText"] : nil;
}];
[migration enumerateObjects:OCTFriend.className block:^(RLMObject *oldObject, RLMObject *newObject) {
newObject[@"name"] = [oldObject[@"name"] length] > 0 ? oldObject[@"name"] : nil;
newObject[@"statusMessage"] = [oldObject[@"statusMessage"] length] > 0 ? oldObject[@"statusMessage"] : nil;
}];
[migration enumerateObjects:OCTFriendRequest.className block:^(RLMObject *oldObject, RLMObject *newObject) {
newObject[@"message"] = [oldObject[@"message"] length] > 0 ? oldObject[@"message"] : nil;
}];
[migration enumerateObjects:OCTMessageFile.className block:^(RLMObject *oldObject, RLMObject *newObject) {
newObject[@"fileName"] = [oldObject[@"fileName"] length] > 0 ? oldObject[@"fileName"] : nil;
newObject[@"fileUTI"] = [oldObject[@"fileUTI"] length] > 0 ? oldObject[@"fileUTI"] : nil;
}];
[migration enumerateObjects:OCTMessageText.className block:^(RLMObject *oldObject, RLMObject *newObject) {
newObject[@"text"] = [oldObject[@"text"] length] > 0 ? oldObject[@"text"] : nil;
}];
}
+ (void)doMigrationVersion5:(RLMMigration *)migration
{
[migration enumerateObjects:OCTMessageAbstract.className block:^(RLMObject *oldObject, RLMObject *newObject) {
newObject[@"chatUniqueIdentifier"] = oldObject[@"chat"][@"uniqueIdentifier"];
newObject[@"senderUniqueIdentifier"] = oldObject[@"sender"][@"uniqueIdentifier"];
}];
}
+ (void)doMigrationVersion7:(RLMMigration *)migration
{
// Before this version OCTMessageText.isDelivered was broken.
// See https://github.com/Antidote-for-Tox/objcTox/issues/158
//
// After update it was fixed + resending of undelivered messages feature was introduced.
// This fired resending all messages that were in history for all friends.
//
// To fix an issue and stop people suffering we mark all outgoing text messages as delivered.
[migration enumerateObjects:OCTMessageAbstract.className block:^(RLMObject *oldObject, RLMObject *newObject) {
if (newObject[@"senderUniqueIdentifier"] != nil) {
return;
}
RLMObject *messageText = newObject[@"messageText"];
if (! messageText) {
return;
}
messageText[@"isDelivered"] = @YES;
}];
}
+ (void)doMigrationVersion8:(RLMMigration *)migration
{
[migration enumerateObjects:OCTFriend.className block:^(RLMObject *oldObject, RLMObject *newObject) {
newObject[@"pushToken"] = nil;
}];
}
+ (void)doMigrationVersion11:(RLMMigration *)migration
{
[migration enumerateObjects:OCTFriend.className block:^(RLMObject *oldObject, RLMObject *newObject) {
newObject[@"msgv3Capability"] = @NO;
}];
}
+ (void)doMigrationVersion12:(RLMMigration *)migration
{
[migration enumerateObjects:OCTMessageText.className block:^(RLMObject *oldObject, RLMObject *newObject) {
newObject[@"msgv3HashHex"] = nil;
}];
}
+ (void)doMigrationVersion13:(RLMMigration *)migration
{
[migration enumerateObjects:OCTMessageText.className block:^(RLMObject *oldObject, RLMObject *newObject) {
newObject[@"sentPush"] = @YES;
}];
}
+ (void)doMigrationVersion14:(RLMMigration *)migration
{
[migration enumerateObjects:OCTMessageAbstract.className block:^(RLMObject *oldObject, RLMObject *newObject) {
newObject[@"tssent"] = @0;
newObject[@"tsrcvd"] = @0;
}];
}
+ (void)doMigrationVersion16:(RLMMigration *)migration
{
[migration enumerateObjects:OCTFriend.className block:^(RLMObject *oldObject, RLMObject *newObject) {
newObject[@"capabilities2"] = nil;
}];
}
/**
* Only one of messageText, messageFile or messageCall can be non-nil.
*/
- (OCTMessageAbstract *)addMessageAbstractWithChat:(OCTChat *)chat
sender:(OCTFriend *)sender
messageText:(OCTMessageText *)messageText
messageFile:(OCTMessageFile *)messageFile
messageCall:(OCTMessageCall *)messageCall
tssent:(UInt32)tssent
tsrcvd:(UInt32)tsrcvd
{
NSParameterAssert(chat);
NSAssert( (messageText && ! messageFile && ! messageCall) ||
(! messageText && messageFile && ! messageCall) ||
(! messageText && ! messageFile && messageCall),
@"Wrong options passed. Only one of messageText, messageFile or messageCall should be non-nil.");
OCTMessageAbstract *messageAbstract = [OCTMessageAbstract new];
messageAbstract.dateInterval = [[NSDate date] timeIntervalSince1970];
messageAbstract.senderUniqueIdentifier = sender.uniqueIdentifier;
messageAbstract.chatUniqueIdentifier = chat.uniqueIdentifier;
messageAbstract.tssent = tssent;
messageAbstract.tsrcvd = tsrcvd;
messageAbstract.messageText = messageText;
messageAbstract.messageFile = messageFile;
messageAbstract.messageCall = messageCall;
[self addObject:messageAbstract];
[self updateObject:chat withBlock:^(OCTChat *theChat) {
theChat.lastMessage = messageAbstract;
theChat.lastActivityDateInterval = messageAbstract.dateInterval;
}];
return messageAbstract;
}
// Delete an NSArray, RLMArray, or RLMResults of messages from this Realm.
- (void)removeMessagesWithSubmessages:(id)messages
{
for (OCTMessageAbstract *message in messages) {
if (message.messageText) {
[self.realm deleteObject:message.messageText];
}
if (message.messageFile) {
[self.realm deleteObject:message.messageFile];
}
if (message.messageCall) {
[self.realm deleteObject:message.messageCall];
}
}
[self.realm deleteObjects:messages];
}
@end