Initial commit
This commit is contained in:
@ -0,0 +1,10 @@
|
||||
// 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 "OCTSubmanagerBootstrap.h"
|
||||
#import "OCTSubmanagerProtocol.h"
|
||||
|
||||
@interface OCTSubmanagerBootstrapImpl : NSObject <OCTSubmanagerBootstrap, OCTSubmanagerProtocol>
|
||||
|
||||
@end
|
@ -0,0 +1,255 @@
|
||||
// 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 "OCTSubmanagerBootstrapImpl.h"
|
||||
#import "OCTNode.h"
|
||||
#import "OCTTox.h"
|
||||
#import "OCTLogging.h"
|
||||
#import "OCTRealmManager.h"
|
||||
#import "OCTSettingsStorageObject.h"
|
||||
|
||||
static const NSTimeInterval kDidConnectDelay = 2.0; // in seconds
|
||||
static const NSTimeInterval kIterationTime = 5.0; // in seconds
|
||||
static const NSUInteger kNodesPerIteration = 20;
|
||||
|
||||
@interface OCTSubmanagerBootstrapImpl ()
|
||||
|
||||
@property (strong, nonatomic) NSMutableSet *addedNodes;
|
||||
|
||||
@property (assign, nonatomic) BOOL isBootstrapping;
|
||||
|
||||
@property (strong, nonatomic) NSObject *bootstrappingLock;
|
||||
|
||||
@property (assign, nonatomic) NSTimeInterval didConnectDelay;
|
||||
@property (assign, nonatomic) NSTimeInterval iterationTime;
|
||||
|
||||
@end
|
||||
|
||||
@implementation OCTSubmanagerBootstrapImpl
|
||||
@synthesize dataSource = _dataSource;
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (! self) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_addedNodes = [NSMutableSet new];
|
||||
_bootstrappingLock = [NSObject new];
|
||||
|
||||
_didConnectDelay = kDidConnectDelay;
|
||||
_iterationTime = kIterationTime;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Public
|
||||
|
||||
- (void)addNodeWithIpv4Host:(nullable NSString *)ipv4Host
|
||||
ipv6Host:(nullable NSString *)ipv6Host
|
||||
udpPort:(OCTToxPort)udpPort
|
||||
tcpPorts:(NSArray<NSNumber *> *)tcpPorts
|
||||
publicKey:(NSString *)publicKey
|
||||
{
|
||||
OCTNode *node = [[OCTNode alloc] initWithIpv4Host:ipv4Host
|
||||
ipv6Host:ipv6Host
|
||||
udpPort:udpPort
|
||||
tcpPorts:tcpPorts
|
||||
publicKey:publicKey];
|
||||
|
||||
@synchronized(self.addedNodes) {
|
||||
[self.addedNodes addObject:node];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addPredefinedNodes
|
||||
{
|
||||
NSString *file = [[self objcToxBundle] pathForResource:@"nodes" ofType:@"json"];
|
||||
NSData *data = [NSData dataWithContentsOfFile:file];
|
||||
|
||||
NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
|
||||
NSAssert(dictionary, @"Nodes json file is corrupted.");
|
||||
|
||||
for (NSDictionary *node in dictionary[@"nodes"]) {
|
||||
NSUInteger lastPing = [node[@"last_ping"] unsignedIntegerValue];
|
||||
|
||||
if (lastPing == 0) {
|
||||
// Skip nodes that weren't seen online.
|
||||
continue;
|
||||
}
|
||||
|
||||
NSString *ipv4 = node[@"ipv4"];
|
||||
NSString *ipv6 = node[@"ipv6"];
|
||||
OCTToxPort udpPort = [node[@"port"] unsignedShortValue];
|
||||
NSArray<NSNumber *> *tcpPorts = node[@"tcp_ports"];
|
||||
NSString *publicKey = node[@"public_key"];
|
||||
|
||||
// Check if addresses are valid.
|
||||
if (ipv4.length <= 2) {
|
||||
ipv4 = nil;
|
||||
}
|
||||
if (ipv6.length <= 2) {
|
||||
ipv6 = nil;
|
||||
}
|
||||
|
||||
NSAssert(ipv4, @"Nodes json file is corrupted");
|
||||
NSAssert(udpPort > 0, @"Nodes json file is corrupted");
|
||||
NSAssert(publicKey, @"Nodes json file is corrupted");
|
||||
|
||||
[self addNodeWithIpv4Host:ipv4 ipv6Host:ipv6 udpPort:udpPort tcpPorts:tcpPorts publicKey:publicKey];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)bootstrap
|
||||
{
|
||||
@synchronized(self.bootstrappingLock) {
|
||||
if (self.isBootstrapping) {
|
||||
OCTLogWarn(@"bootstrap method called while already bootstrapping");
|
||||
return;
|
||||
}
|
||||
self.isBootstrapping = YES;
|
||||
}
|
||||
|
||||
OCTLogVerbose(@"bootstrapping with %lu nodes", (unsigned long)self.addedNodes.count);
|
||||
|
||||
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
|
||||
|
||||
if (realmManager.settingsStorage.bootstrapDidConnect) {
|
||||
OCTLogVerbose(@"did connect before, waiting %g seconds", self.didConnectDelay);
|
||||
[self tryToBootstrapAfter:self.didConnectDelay];
|
||||
}
|
||||
else {
|
||||
[self tryToBootstrap];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)tryToBootstrapAfter:(NSTimeInterval)after
|
||||
{
|
||||
__weak OCTSubmanagerBootstrapImpl *weakSelf = self;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, after * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||
__strong OCTSubmanagerBootstrapImpl *strongSelf = weakSelf;
|
||||
|
||||
if (! strongSelf) {
|
||||
OCTLogInfo(@"OCTSubmanagerBootstrap is dead, seems that OCTManager was killed, quiting.");
|
||||
return;
|
||||
}
|
||||
|
||||
[strongSelf tryToBootstrap];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)tryToBootstrap
|
||||
{
|
||||
if ([self.dataSource managerIsToxConnected]) {
|
||||
OCTLogInfo(@"trying to bootstrap... tox is connected, exiting");
|
||||
|
||||
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
|
||||
[realmManager updateObject:realmManager.settingsStorage withBlock:^(OCTSettingsStorageObject *object) {
|
||||
object.bootstrapDidConnect = YES;
|
||||
}];
|
||||
|
||||
[self finishBootstrapping];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
NSArray *selectedNodes = [self selectedNodesForIteration];
|
||||
|
||||
if (! selectedNodes.count) {
|
||||
OCTLogInfo(@"trying to bootstrap... no nodes left, exiting");
|
||||
[self finishBootstrapping];
|
||||
return;
|
||||
}
|
||||
|
||||
OCTLogInfo(@"trying to bootstrap... picked %lu nodes", (unsigned long)selectedNodes.count);
|
||||
|
||||
for (OCTNode *node in selectedNodes) {
|
||||
// HINT: do not do this async on a thread. since "node" will loose its value
|
||||
// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
|
||||
[self safeBootstrapFromHost:node.ipv4Host port:node.udpPort publicKey:node.publicKey];
|
||||
[self safeBootstrapFromHost:node.ipv6Host port:node.udpPort publicKey:node.publicKey];
|
||||
|
||||
for (NSNumber *tcpPort in node.tcpPorts) {
|
||||
[self safeAddTcpRelayWithHost:node.ipv4Host port:tcpPort.intValue publicKey:node.publicKey];
|
||||
[self safeAddTcpRelayWithHost:node.ipv6Host port:tcpPort.intValue publicKey:node.publicKey];
|
||||
}
|
||||
// });
|
||||
}
|
||||
|
||||
[self tryToBootstrapAfter:self.iterationTime];
|
||||
}
|
||||
|
||||
- (void)safeBootstrapFromHost:(NSString *)host port:(OCTToxPort)port publicKey:(NSString *)publicKey
|
||||
{
|
||||
if (! host) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSError *error;
|
||||
|
||||
if (! [[self.dataSource managerGetTox] bootstrapFromHost:host port:port publicKey:publicKey error:&error]) {
|
||||
OCTLogWarn(@"trying to bootstrap... bootstrap failed with address %@, error %@", host, error);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)safeAddTcpRelayWithHost:(NSString *)host port:(OCTToxPort)port publicKey:(NSString *)publicKey
|
||||
{
|
||||
if (! host) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSError *error;
|
||||
|
||||
if (! [[self.dataSource managerGetTox] addTCPRelayWithHost:host port:port publicKey:publicKey error:&error]) {
|
||||
OCTLogWarn(@"trying to bootstrap... tcp relay failed with address %@, error %@", host, error);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)finishBootstrapping
|
||||
{
|
||||
@synchronized(self.bootstrappingLock) {
|
||||
self.isBootstrapping = NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)selectedNodesForIteration
|
||||
{
|
||||
NSMutableArray *allNodes;
|
||||
NSMutableArray *selectedNodes = [NSMutableArray new];
|
||||
|
||||
@synchronized(self.addedNodes) {
|
||||
allNodes = [[self.addedNodes allObjects] mutableCopy];
|
||||
}
|
||||
|
||||
while (allNodes.count && (selectedNodes.count < kNodesPerIteration)) {
|
||||
NSUInteger index = arc4random_uniform((u_int32_t)allNodes.count);
|
||||
|
||||
[selectedNodes addObject:allNodes[index]];
|
||||
[allNodes removeObjectAtIndex:index];
|
||||
}
|
||||
|
||||
@synchronized(self.addedNodes) {
|
||||
[self.addedNodes minusSet:[NSSet setWithArray:selectedNodes]];
|
||||
}
|
||||
|
||||
return [selectedNodes copy];
|
||||
}
|
||||
|
||||
- (NSBundle *)objcToxBundle
|
||||
{
|
||||
NSBundle *mainBundle = [NSBundle bundleForClass:[self class]];
|
||||
NSBundle *objcToxBundle = [NSBundle bundleWithPath:[mainBundle pathForResource:@"objcTox" ofType:@"bundle"]];
|
||||
|
||||
// objcToxBundle is used when installed with CocoaPods. If we run tests/demo app mainBundle would be used.
|
||||
return objcToxBundle ?: mainBundle;
|
||||
}
|
||||
|
||||
@end
|
@ -0,0 +1,27 @@
|
||||
// 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 "OCTSubmanagerCalls.h"
|
||||
#import "OCTSubmanagerProtocol.h"
|
||||
#import "OCTToxAV.h"
|
||||
#import "OCTManagerConstants.h"
|
||||
#import "OCTAudioEngine.h"
|
||||
#import "OCTVideoEngine.h"
|
||||
#import "OCTRealmManager.h"
|
||||
#import "OCTCall+Utilities.h"
|
||||
#import "OCTCallTimer.h"
|
||||
|
||||
@class OCTTox;
|
||||
|
||||
@interface OCTSubmanagerCallsImpl : NSObject <OCTSubmanagerCalls, OCTSubmanagerProtocol>
|
||||
|
||||
/**
|
||||
* Initialize the OCTSubmanagerCall
|
||||
*/
|
||||
- (instancetype)initWithTox:(OCTTox *)tox;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
+ (instancetype)new NS_UNAVAILABLE;
|
||||
|
||||
@end
|
@ -0,0 +1,564 @@
|
||||
// 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 "OCTSubmanagerCallsImpl.h"
|
||||
#import "OCTLogging.h"
|
||||
#import "OCTTox.h"
|
||||
|
||||
const OCTToxAVAudioBitRate kDefaultAudioBitRate = OCTToxAVAudioBitRate48;
|
||||
const OCTToxAVVideoBitRate kDefaultVideoBitRate = 2500;
|
||||
|
||||
@interface OCTSubmanagerCallsImpl () <OCTToxAVDelegate>
|
||||
|
||||
@property (strong, nonatomic) OCTToxAV *toxAV;
|
||||
@property (strong, nonatomic) OCTAudioEngine *audioEngine;
|
||||
@property (strong, nonatomic) OCTVideoEngine *videoEngine;
|
||||
@property (strong, nonatomic) OCTCallTimer *timer;
|
||||
@property (nonatomic, assign) dispatch_once_t setupOnceToken;
|
||||
|
||||
@end
|
||||
|
||||
@implementation OCTSubmanagerCallsImpl : NSObject
|
||||
@synthesize delegate = _delegate;
|
||||
@synthesize dataSource = _dataSource;
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)initWithTox:(OCTTox *)tox
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (! self) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_toxAV = [[OCTToxAV alloc] initWithTox:tox error:nil];
|
||||
_toxAV.delegate = self;
|
||||
[_toxAV start];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self endAllCalls];
|
||||
}
|
||||
|
||||
#pragma mark - Public
|
||||
|
||||
- (BOOL)setupAndReturnError:(NSError **)error
|
||||
{
|
||||
NSAssert(self.dataSource, @"dataSource is needed before setup of OCTSubmanagerCalls");
|
||||
__block BOOL status = NO;
|
||||
dispatch_once(&_setupOnceToken, ^{
|
||||
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
|
||||
self.timer = [[OCTCallTimer alloc] initWithRealmManager:realmManager];
|
||||
|
||||
self.audioEngine = [OCTAudioEngine new];
|
||||
self.audioEngine.toxav = self.toxAV;
|
||||
self.videoEngine = [OCTVideoEngine new];
|
||||
self.videoEngine.toxav = self.toxAV;
|
||||
|
||||
status = [self.videoEngine setupAndReturnError:error];
|
||||
});
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
- (OCTCall *)callToChat:(OCTChat *)chat enableAudio:(BOOL)enableAudio enableVideo:(BOOL)enableVideo error:(NSError **)error
|
||||
{
|
||||
OCTToxAVAudioBitRate audioBitRate = (enableAudio) ? kDefaultAudioBitRate : OCTToxAVAudioBitRateDisabled;
|
||||
OCTToxAVVideoBitRate videoBitRate = (enableVideo) ? kDefaultVideoBitRate : kOCTToxAVVideoBitRateDisable;
|
||||
|
||||
|
||||
if (chat.friends.count == 1) {
|
||||
OCTFriend *friend = chat.friends.lastObject;
|
||||
self.audioEngine.friendNumber = friend.friendNumber;
|
||||
|
||||
if (! [self.toxAV callFriendNumber:friend.friendNumber
|
||||
audioBitRate:audioBitRate
|
||||
videoBitRate:videoBitRate
|
||||
error:error]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
[self checkForCurrentActiveCallAndPause];
|
||||
|
||||
OCTCall *call = [self createCallWithFriend:friend status:OCTCallStatusDialing];
|
||||
|
||||
OCTRealmManager *manager = [self.dataSource managerGetRealmManager];
|
||||
[manager updateObject:call withBlock:^(OCTCall *callToUpdate) {
|
||||
callToUpdate.status = OCTCallStatusDialing;
|
||||
callToUpdate.videoIsEnabled = enableVideo;
|
||||
}];
|
||||
|
||||
self.enableMicrophone = YES;
|
||||
|
||||
return call;
|
||||
}
|
||||
else {
|
||||
// TO DO: Group Calls
|
||||
return nil;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)enableVideoSending:(BOOL)enable forCall:(OCTCall *)call error:(NSError **)error
|
||||
{
|
||||
OCTToxAVVideoBitRate bitrate = (enable) ? kDefaultVideoBitRate : kOCTToxAVVideoBitRateDisable;
|
||||
if (! [self setVideoBitrate:bitrate forCall:call error:error]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (enable && (! [call isPaused])) {
|
||||
OCTFriend *friend = [call.chat.friends firstObject];
|
||||
self.videoEngine.friendNumber = friend.friendNumber;
|
||||
[self.videoEngine startSendingVideo];
|
||||
}
|
||||
else {
|
||||
[self.videoEngine stopSendingVideo];
|
||||
}
|
||||
|
||||
OCTRealmManager *manager = [self.dataSource managerGetRealmManager];
|
||||
[manager updateObject:call withBlock:^(OCTCall *callToUpdate) {
|
||||
callToUpdate.videoIsEnabled = enable;
|
||||
}];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)answerCall:(OCTCall *)call enableAudio:(BOOL)enableAudio enableVideo:(BOOL)enableVideo error:(NSError **)error
|
||||
{
|
||||
OCTToxAVAudioBitRate audioBitRate = (enableAudio) ? kDefaultAudioBitRate : OCTToxAVAudioBitRateDisabled;
|
||||
OCTToxAVVideoBitRate videoBitRate = (enableVideo) ? kDefaultVideoBitRate : kOCTToxAVVideoBitRateDisable;
|
||||
|
||||
if (call.chat.friends.count == 1) {
|
||||
|
||||
OCTFriend *friend = call.chat.friends.firstObject;
|
||||
|
||||
if (! [self.toxAV answerIncomingCallFromFriend:friend.friendNumber
|
||||
audioBitRate:audioBitRate
|
||||
videoBitRate:videoBitRate
|
||||
error:error]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
[self checkForCurrentActiveCallAndPause];
|
||||
|
||||
OCTRealmManager *manager = [self.dataSource managerGetRealmManager];
|
||||
[manager updateObject:call withBlock:^(OCTCall *callToUpdate) {
|
||||
call.status = OCTCallStatusActive;
|
||||
callToUpdate.videoIsEnabled = enableVideo;
|
||||
}];
|
||||
|
||||
self.enableMicrophone = YES;
|
||||
[self startEnginesAndTimer:YES forCall:call];
|
||||
|
||||
return YES;
|
||||
}
|
||||
else {
|
||||
// TO DO: Group Calls
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)enableMicrophone
|
||||
{
|
||||
return self.audioEngine.enableMicrophone;
|
||||
}
|
||||
|
||||
- (void)setEnableMicrophone:(BOOL)enableMicrophone
|
||||
{
|
||||
self.audioEngine.enableMicrophone = enableMicrophone;
|
||||
}
|
||||
|
||||
- (BOOL)sendCallControl:(OCTToxAVCallControl)control toCall:(OCTCall *)call error:(NSError **)error
|
||||
{
|
||||
if (call.chat.friends.count == 1) {
|
||||
|
||||
OCTFriend *friend = call.chat.friends.firstObject;
|
||||
|
||||
if (! [self.toxAV sendCallControl:control toFriendNumber:friend.friendNumber error:error]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
switch (control) {
|
||||
case OCTToxAVCallControlResume:
|
||||
[self checkForCurrentActiveCallAndPause];
|
||||
[self putOnPause:NO call:call];
|
||||
break;
|
||||
case OCTToxAVCallControlCancel:
|
||||
[self addMessageAndDeleteCall:call];
|
||||
|
||||
if ((self.audioEngine.friendNumber == friend.friendNumber) &&
|
||||
([self.audioEngine isAudioRunning:nil])) {
|
||||
[self startEnginesAndTimer:NO forCall:call];
|
||||
}
|
||||
|
||||
break;
|
||||
case OCTToxAVCallControlPause:
|
||||
[self putOnPause:YES call:call];
|
||||
break;
|
||||
case OCTToxAVCallControlUnmuteAudio:
|
||||
break;
|
||||
case OCTToxAVCallControlMuteAudio:
|
||||
break;
|
||||
case OCTToxAVCallControlHideVideo:
|
||||
break;
|
||||
case OCTToxAVCallControlShowVideo:
|
||||
break;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
else {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (OCTView *)videoFeed
|
||||
{
|
||||
return [self.videoEngine videoFeed];
|
||||
}
|
||||
|
||||
- (void)getVideoCallPreview:(void (^)(CALayer *))completionBlock
|
||||
{
|
||||
[self.videoEngine getVideoCallPreview:completionBlock];
|
||||
}
|
||||
|
||||
- (BOOL)setAudioBitrate:(int)bitrate forCall:(OCTCall *)call error:(NSError **)error
|
||||
{
|
||||
if (call.chat.friends.count == 1) {
|
||||
|
||||
OCTFriend *friend = call.chat.friends.firstObject;
|
||||
|
||||
return [self.toxAV setAudioBitRate:bitrate force:NO forFriend:friend.friendNumber error:error];
|
||||
}
|
||||
else {
|
||||
// TO DO: Group Calls
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Setting IO devices
|
||||
|
||||
#if ! TARGET_OS_IPHONE
|
||||
|
||||
- (BOOL)setAudioInputDevice:(NSString *)deviceUniqueID error:(NSError **)error
|
||||
{
|
||||
return [self.audioEngine setInputDeviceID:deviceUniqueID error:error];
|
||||
}
|
||||
|
||||
- (BOOL)setAudioOutputDevice:(NSString *)deviceUniqueID error:(NSError **)error
|
||||
{
|
||||
return [self.audioEngine setOutputDeviceID:deviceUniqueID error:error];
|
||||
}
|
||||
|
||||
- (BOOL)setVideoInputDevice:(NSString *)deviceUniqueID error:(NSError **)error
|
||||
{
|
||||
return [self.videoEngine switchToCamera:deviceUniqueID error:error];
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
- (BOOL)routeAudioToSpeaker:(BOOL)speaker error:(NSError **)error
|
||||
{
|
||||
return [self.audioEngine routeAudioToSpeaker:speaker error:error];
|
||||
}
|
||||
|
||||
- (BOOL)switchToCameraFront:(BOOL)front error:(NSError **)error
|
||||
{
|
||||
return [self.videoEngine useFrontCamera:front error:error];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#pragma mark Private methods
|
||||
|
||||
- (OCTCall *)createCallWithFriend:(OCTFriend *)friend status:(OCTCallStatus)status
|
||||
{
|
||||
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
|
||||
|
||||
OCTChat *chat = [realmManager getOrCreateChatWithFriend:friend];
|
||||
|
||||
return [realmManager createCallWithChat:chat status:status];
|
||||
}
|
||||
|
||||
- (OCTCall *)getCurrentCallForFriendNumber:(OCTToxFriendNumber)friendNumber
|
||||
{
|
||||
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
|
||||
|
||||
NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil];
|
||||
OCTFriend *friend = [realmManager friendWithPublicKey:publicKey];
|
||||
OCTChat *chat = [realmManager getOrCreateChatWithFriend:friend];
|
||||
|
||||
return [realmManager getCurrentCallForChat:chat];
|
||||
}
|
||||
|
||||
- (void)updateCall:(OCTCall *)call withStatus:(OCTCallStatus)status
|
||||
{
|
||||
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
|
||||
|
||||
[realmManager updateObject:call withBlock:^(OCTCall *callToUpdate) {
|
||||
callToUpdate.status = status;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)endAllCalls
|
||||
{
|
||||
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
|
||||
|
||||
[self startEnginesAndTimer:NO forCall:nil];
|
||||
|
||||
RLMResults *calls = [realmManager objectsWithClass:[OCTCall class] predicate:nil];
|
||||
|
||||
for (OCTCall *call in calls) {
|
||||
OCTFriend *friend = call.chat.friends.firstObject;
|
||||
[self.toxAV sendCallControl:OCTToxAVCallControlCancel toFriendNumber:friend.friendNumber error:nil];
|
||||
}
|
||||
|
||||
[realmManager convertAllCallsToMessages];
|
||||
}
|
||||
|
||||
- (void)putOnPause:(BOOL)pause call:(OCTCall *)call
|
||||
{
|
||||
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
|
||||
BOOL wasPaused = call.pausedStatus != OCTCallPausedStatusNone;
|
||||
|
||||
if (pause) {
|
||||
if (! wasPaused) {
|
||||
[self startEnginesAndTimer:NO forCall:call];
|
||||
}
|
||||
}
|
||||
else {
|
||||
OCTFriend *friend = [call.chat.friends firstObject];
|
||||
self.audioEngine.friendNumber = friend.friendNumber;
|
||||
|
||||
if (call.pausedStatus == OCTCallPausedStatusByUser) {
|
||||
[self startEnginesAndTimer:YES forCall:call];
|
||||
}
|
||||
}
|
||||
|
||||
[realmManager updateObject:call withBlock:^(OCTCall *callToUpdate) {
|
||||
if (pause) {
|
||||
callToUpdate.pausedStatus |= OCTCallPausedStatusByUser;
|
||||
callToUpdate.onHoldStartInterval = callToUpdate.onHoldStartInterval ?: [[NSDate date] timeIntervalSince1970];
|
||||
}
|
||||
else {
|
||||
callToUpdate.pausedStatus &= ~OCTCallPausedStatusByUser;
|
||||
callToUpdate.onHoldStartInterval = 0;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)addMessageAndDeleteCall:(OCTCall *)call
|
||||
{
|
||||
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
|
||||
[realmManager addMessageCall:call];
|
||||
|
||||
if (! [call isPaused]) {
|
||||
[self.timer stopTimer];
|
||||
}
|
||||
|
||||
// TODO: this one crashes when video was active, and is called directly
|
||||
// there is a race condition somewhere -> fix me!
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||
[realmManager deleteObject:call];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)updateCall:(OCTCall *)call withState:(OCTToxAVCallState)state pausedStatus:(OCTCallPausedStatus)pausedStatus
|
||||
{
|
||||
BOOL sendingAudio = NO, sendingVideo = NO, acceptingAudio = NO, acceptingVideo = NO;
|
||||
|
||||
if (state & OCTToxAVFriendCallStateAcceptingAudio) {
|
||||
acceptingAudio = YES;
|
||||
}
|
||||
|
||||
if (state & OCTToxAVFriendCallStateAcceptingVideo) {
|
||||
acceptingVideo = YES;
|
||||
}
|
||||
|
||||
if (state & OCTToxAVFriendCallStateSendingAudio) {
|
||||
sendingAudio = YES;
|
||||
}
|
||||
|
||||
if (state & OCTToxAVFriendCallStateSendingVideo) {
|
||||
sendingVideo = YES;
|
||||
}
|
||||
|
||||
BOOL wasPaused = call.pausedStatus != OCTCallPausedStatusNone;
|
||||
|
||||
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
|
||||
[realmManager updateObject:call withBlock:^(OCTCall *callToUpdate) {
|
||||
callToUpdate.friendAcceptingAudio = acceptingAudio;
|
||||
callToUpdate.friendAcceptingVideo = acceptingVideo;
|
||||
callToUpdate.friendSendingAudio = sendingAudio;
|
||||
callToUpdate.friendSendingVideo = sendingVideo;
|
||||
callToUpdate.pausedStatus = pausedStatus;
|
||||
|
||||
if (! wasPaused && (state == OCTToxAVFriendCallStatePaused)) {
|
||||
callToUpdate.onHoldStartInterval = [[NSDate date] timeIntervalSince1970];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)checkForCurrentActiveCallAndPause
|
||||
{
|
||||
if ([self.audioEngine isAudioRunning:nil] || [self.videoEngine isSendingVideo]) {
|
||||
OCTCall *call = [self getCurrentCallForFriendNumber:self.audioEngine.friendNumber];
|
||||
[self sendCallControl:OCTToxAVCallControlPause toCall:call error:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)setVideoBitrate:(int)bitrate forCall:(OCTCall *)call error:(NSError **)error
|
||||
{
|
||||
if (call.chat.friends.count == 1) {
|
||||
|
||||
OCTFriend *friend = call.chat.friends.firstObject;
|
||||
|
||||
return [self.toxAV setVideoBitRate:bitrate force:NO forFriend:friend.friendNumber error:error];
|
||||
}
|
||||
else {
|
||||
// TO DO: Group Calls
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)startEnginesAndTimer:(BOOL)start forCall:(OCTCall *)call
|
||||
{
|
||||
if (start) {
|
||||
OCTFriend *friend = [call.chat.friends firstObject];
|
||||
|
||||
NSError *error;
|
||||
if (! [self.audioEngine startAudioFlow:&error]) {
|
||||
OCTLogVerbose(@"Error starting audio flow %@", error);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (call.videoIsEnabled) {
|
||||
[self.videoEngine startSendingVideo];
|
||||
}
|
||||
|
||||
self.audioEngine.friendNumber = friend.friendNumber;
|
||||
self.videoEngine.friendNumber = friend.friendNumber;
|
||||
|
||||
[self.timer startTimerForCall:call];
|
||||
}
|
||||
else {
|
||||
[self.audioEngine stopAudioFlow:nil];
|
||||
[self.videoEngine stopSendingVideo];
|
||||
[self.timer stopTimer];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark OCTToxAV delegate methods
|
||||
|
||||
- (void)toxAV:(OCTToxAV *)toxAV receiveCallAudioEnabled:(BOOL)audio videoEnabled:(BOOL)video friendNumber:(OCTToxFriendNumber)friendNumber
|
||||
{
|
||||
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
|
||||
|
||||
NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil];
|
||||
OCTFriend *friend = [realmManager friendWithPublicKey:publicKey];
|
||||
OCTCall *call = [self createCallWithFriend:friend status:OCTCallStatusRinging];
|
||||
|
||||
[realmManager updateObject:call withBlock:^(OCTCall *callToUpdate) {
|
||||
callToUpdate.status = OCTCallStatusRinging;
|
||||
callToUpdate.caller = friend;
|
||||
callToUpdate.friendSendingAudio = audio;
|
||||
callToUpdate.friendAcceptingAudio = audio;
|
||||
callToUpdate.friendSendingVideo = video;
|
||||
callToUpdate.friendAcceptingVideo = video;
|
||||
}];
|
||||
|
||||
if ([self.delegate respondsToSelector:@selector(callSubmanager:receiveCall:audioEnabled:videoEnabled:)]) {
|
||||
[self.delegate callSubmanager:self receiveCall:call audioEnabled:audio videoEnabled:video];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)toxAV:(OCTToxAV *)toxAV callStateChanged:(OCTToxAVCallState)state friendNumber:(OCTToxFriendNumber)friendNumber
|
||||
{
|
||||
OCTCall *call = [self getCurrentCallForFriendNumber:friendNumber];
|
||||
|
||||
if ((state & OCTToxAVFriendCallStateFinished) || (state & OCTToxAVFriendCallStateError)) {
|
||||
|
||||
[self addMessageAndDeleteCall:call];
|
||||
|
||||
if ((self.audioEngine.friendNumber == friendNumber) && [self.audioEngine isAudioRunning:nil]) {
|
||||
[self.audioEngine stopAudioFlow:nil];
|
||||
}
|
||||
|
||||
if ((self.videoEngine.friendNumber == friendNumber) && [self.videoEngine isSendingVideo]) {
|
||||
[self.videoEngine stopSendingVideo];
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (call.status == OCTCallStatusDialing) {
|
||||
[self updateCall:call withStatus:OCTCallStatusActive];
|
||||
[self startEnginesAndTimer:YES forCall:call];
|
||||
}
|
||||
|
||||
OCTCallPausedStatus pauseStatus = call.pausedStatus;
|
||||
|
||||
if ((pauseStatus == OCTCallPausedStatusNone) && (state == OCTToxAVFriendCallStatePaused)) {
|
||||
[self startEnginesAndTimer:NO forCall:call];
|
||||
}
|
||||
|
||||
if ((pauseStatus == OCTCallPausedStatusByFriend) && (state != OCTToxAVFriendCallStatePaused)) {
|
||||
[self startEnginesAndTimer:YES forCall:call];
|
||||
}
|
||||
|
||||
if (state == OCTToxAVFriendCallStatePaused) {
|
||||
pauseStatus |= OCTCallPausedStatusByFriend;
|
||||
}
|
||||
else {
|
||||
pauseStatus &= ~OCTCallPausedStatusByFriend;
|
||||
}
|
||||
|
||||
[self updateCall:call withState:state pausedStatus:pauseStatus];
|
||||
}
|
||||
|
||||
- (void) toxAV:(OCTToxAV *)toxAV
|
||||
receiveAudio:(OCTToxAVPCMData *)pcm
|
||||
sampleCount:(OCTToxAVSampleCount)sampleCount
|
||||
channels:(OCTToxAVChannels)channels
|
||||
sampleRate:(OCTToxAVSampleRate)sampleRate
|
||||
friendNumber:(OCTToxFriendNumber)friendNumber
|
||||
{
|
||||
// TOXAUDIO: -incoming-audio-
|
||||
[self.audioEngine provideAudioFrames:pcm sampleCount:sampleCount channels:channels sampleRate:sampleRate fromFriend:friendNumber];
|
||||
}
|
||||
|
||||
- (void)toxAV:(OCTToxAV *)toxAV audioBitRateStatus:(OCTToxAVAudioBitRate)audioBitRate forFriendNumber:(OCTToxFriendNumber)friendNumber
|
||||
{
|
||||
// TODO https://github.com/Antidote-for-Tox/objcTox/issues/88
|
||||
}
|
||||
|
||||
- (void)toxAV:(OCTToxAV *)toxAV videoBitRateStatus:(OCTToxAVVideoBitRate)audioBitRate forFriendNumber:(OCTToxFriendNumber)friendNumber
|
||||
{
|
||||
// TODO https://github.com/Antidote-for-Tox/objcTox/issues/88
|
||||
}
|
||||
|
||||
- (void) toxAV:(OCTToxAV *)toxAV
|
||||
receiveVideoFrameWithWidth:(OCTToxAVVideoWidth)width height:(OCTToxAVVideoHeight)height
|
||||
yPlane:(OCTToxAVPlaneData *)yPlane uPlane:(OCTToxAVPlaneData *)uPlane
|
||||
vPlane:(OCTToxAVPlaneData *)vPlane
|
||||
yStride:(OCTToxAVStrideData)yStride uStride:(OCTToxAVStrideData)uStride
|
||||
vStride:(OCTToxAVStrideData)vStride
|
||||
friendNumber:(OCTToxFriendNumber)friendNumber
|
||||
{
|
||||
[self.videoEngine receiveVideoFrameWithWidth:width
|
||||
height:height
|
||||
yPlane:yPlane
|
||||
uPlane:uPlane
|
||||
vPlane:vPlane
|
||||
yStride:yStride
|
||||
uStride:uStride
|
||||
vStride:vStride
|
||||
friendNumber:friendNumber];
|
||||
}
|
||||
|
||||
@end
|
@ -0,0 +1,10 @@
|
||||
// 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 "OCTSubmanagerChats.h"
|
||||
#import "OCTSubmanagerProtocol.h"
|
||||
|
||||
@interface OCTSubmanagerChatsImpl : NSObject <OCTSubmanagerChats, OCTSubmanagerProtocol>
|
||||
|
||||
@end
|
@ -0,0 +1,529 @@
|
||||
// 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 "OCTSubmanagerChatsImpl.h"
|
||||
#import "OCTTox.h"
|
||||
#import "OCTRealmManager.h"
|
||||
#import "OCTMessageAbstract.h"
|
||||
#import "OCTMessageText.h"
|
||||
#import "OCTChat.h"
|
||||
#import "OCTLogging.h"
|
||||
#import "OCTSendMessageOperation.h"
|
||||
#import "OCTTox+Private.h"
|
||||
#import "OCTToxOptions+Private.h"
|
||||
#import "Firebase.h"
|
||||
|
||||
@interface OCTSubmanagerChatsImpl ()
|
||||
|
||||
@property (strong, nonatomic, readonly) NSOperationQueue *sendMessageQueue;
|
||||
|
||||
@end
|
||||
|
||||
@implementation OCTSubmanagerChatsImpl
|
||||
@synthesize dataSource = _dataSource;
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (! self) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_sendMessageQueue = [NSOperationQueue new];
|
||||
_sendMessageQueue.maxConcurrentOperationCount = 1;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self.dataSource.managerGetNotificationCenter removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)configure
|
||||
{
|
||||
[self.dataSource.managerGetNotificationCenter addObserver:self
|
||||
selector:@selector(friendConnectionStatusChangeNotification:)
|
||||
name:kOCTFriendConnectionStatusChangeNotification
|
||||
object:nil];
|
||||
}
|
||||
|
||||
#pragma mark - Public
|
||||
|
||||
- (OCTChat *)getOrCreateChatWithFriend:(OCTFriend *)friend
|
||||
{
|
||||
return [[self.dataSource managerGetRealmManager] getOrCreateChatWithFriend:friend];
|
||||
}
|
||||
|
||||
- (void)removeMessages:(NSArray<OCTMessageAbstract *> *)messages
|
||||
{
|
||||
[[self.dataSource managerGetRealmManager] removeMessages:messages];
|
||||
[self.dataSource.managerGetNotificationCenter postNotificationName:kOCTScheduleFileTransferCleanupNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)removeAllMessagesInChat:(OCTChat *)chat removeChat:(BOOL)removeChat
|
||||
{
|
||||
[[self.dataSource managerGetRealmManager] removeAllMessagesInChat:chat removeChat:removeChat];
|
||||
[self.dataSource.managerGetNotificationCenter postNotificationName:kOCTScheduleFileTransferCleanupNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)sendOwnPush
|
||||
{
|
||||
NSLog(@"PUSH:sendOwnPush");
|
||||
NSString *token = [FIRMessaging messaging].FCMToken;
|
||||
if (token.length > 0)
|
||||
{
|
||||
NSString *my_pushToken = [NSString stringWithFormat:@"https://tox.zoff.xyz/toxfcm/fcm.php?id=%@&type=1", token];
|
||||
// NSLog(@"token push url=%@", my_pushToken);
|
||||
triggerPush(my_pushToken, nil, nil, nil);
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog(@"PUSH:sendOwnPush:no token");
|
||||
}
|
||||
}
|
||||
|
||||
- (void)sendMessagePushToChat:(OCTChat *)chat
|
||||
{
|
||||
NSParameterAssert(chat);
|
||||
NSLog(@"PUSH:sendMessagePushToChat");
|
||||
__weak OCTSubmanagerChatsImpl *weakSelf = self;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
__strong OCTSubmanagerChatsImpl *strongSelf = weakSelf;
|
||||
OCTRealmManager *realmManager = [strongSelf.dataSource managerGetRealmManager];
|
||||
|
||||
OCTFriend *friend = [chat.friends firstObject];
|
||||
__block NSString *friend_pushToken = friend.pushToken;
|
||||
|
||||
if (friend_pushToken == nil)
|
||||
{
|
||||
NSLog(@"sendMessagePushToChat:Friend has No Pushtoken");
|
||||
}
|
||||
else
|
||||
{
|
||||
// HINT: only select outgoing messages (senderUniqueIdentifier == NULL)
|
||||
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"chatUniqueIdentifier == %@ AND messageText.isDelivered == 0 AND messageText.sentPush == 0 AND senderUniqueIdentifier == nil", chat.uniqueIdentifier];
|
||||
|
||||
RLMResults *results = [realmManager objectsWithClass:[OCTMessageAbstract class] predicate:predicate];
|
||||
OCTMessageAbstract *message_found = [results firstObject];
|
||||
if (message_found) {
|
||||
|
||||
[realmManager updateObject:message_found withBlock:^(OCTMessageAbstract *theMessage) {
|
||||
theMessage.messageText.sentPush = YES;
|
||||
}];
|
||||
triggerPush(friend_pushToken, message_found.messageText.msgv3HashHex, strongSelf, chat);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
triggerPush(NSString *used_pushToken,
|
||||
NSString *msgv3HashHex,
|
||||
OCTSubmanagerChatsImpl *strongSelf,
|
||||
OCTChat *chat)
|
||||
{
|
||||
// HINT: call push token (URL) here
|
||||
// best in a background thread
|
||||
//
|
||||
NSLog(@"PUSH:triggerPush");
|
||||
if ((used_pushToken != nil) && (used_pushToken.length > 5)) {
|
||||
|
||||
// check push url starts with allowed values
|
||||
if (
|
||||
([used_pushToken hasPrefix:@"https://tox.zoff.xyz/toxfcm/fcm.php?id="])
|
||||
||
|
||||
([used_pushToken hasPrefix:@"https://gotify1.unifiedpush.org/UP?token="])
|
||||
||
|
||||
([used_pushToken hasPrefix:@"https://ntfy.sh/"])
|
||||
) {
|
||||
|
||||
NSString *strong_pushToken = used_pushToken;
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
// NSString *strong_pushToken = weak_pushToken;
|
||||
int PUSH_URL_TRIGGER_AGAIN_MAX_COUNT = 8;
|
||||
int PUSH_URL_TRIGGER_AGAIN_SECONDS = 21;
|
||||
|
||||
for (int i=0; i<(PUSH_URL_TRIGGER_AGAIN_MAX_COUNT + 1); i++)
|
||||
{
|
||||
if (chat == nil) {
|
||||
__block UIApplicationState as = UIApplicationStateBackground;
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
as =[[UIApplication sharedApplication] applicationState];
|
||||
});
|
||||
|
||||
if (as == UIApplicationStateActive) {
|
||||
NSLog(@"PUSH:fg->break:1");
|
||||
break;
|
||||
}
|
||||
}
|
||||
NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:strong_pushToken]];
|
||||
NSString *userUpdate = [NSString stringWithFormat:@"&text=1", nil];
|
||||
[urlRequest setHTTPMethod:@"POST"];
|
||||
|
||||
NSData *data1 = [userUpdate dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
[urlRequest setHTTPBody:data1];
|
||||
[urlRequest setCachePolicy:NSURLRequestReloadIgnoringCacheData];
|
||||
[urlRequest setTimeoutInterval:10]; // HINT: 10 seconds
|
||||
NSString *userAgent = @"Mozilla/5.0 (Windows NT 6.1; rv:60.0) Gecko/20100101 Firefox/60.0";
|
||||
[urlRequest setValue:userAgent forHTTPHeaderField:@"User-Agent"];
|
||||
[urlRequest setValue:@"no-cache, no-store, must-revalidate" forHTTPHeaderField:@"Cache-Control"];
|
||||
[urlRequest setValue:@"no-cache" forHTTPHeaderField:@"Pragma"];
|
||||
[urlRequest setValue:@"0" forHTTPHeaderField:@"Expires"];
|
||||
|
||||
// NSLog(@"PUSH:for msgv3HashHex=%@", msgv3HashHex);
|
||||
// NSLog(@"PUSH:for friend.pushToken=%@", strong_pushToken);
|
||||
|
||||
NSURLSession *session = [NSURLSession sharedSession];
|
||||
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
|
||||
if ((httpResponse.statusCode < 300) && (httpResponse.statusCode > 199)) {
|
||||
NSLog(@"calling PUSH URL:CALL:SUCCESS");
|
||||
}
|
||||
else {
|
||||
NSLog(@"calling PUSH URL:-ERROR:01-");
|
||||
}
|
||||
}];
|
||||
NSLog(@"calling PUSH URL:CALL:start");
|
||||
[dataTask resume];
|
||||
if (i < PUSH_URL_TRIGGER_AGAIN_MAX_COUNT)
|
||||
{
|
||||
NSLog(@"calling PUSH URL:WAIT:start");
|
||||
[NSThread sleepForTimeInterval:PUSH_URL_TRIGGER_AGAIN_SECONDS];
|
||||
NSLog(@"calling PUSH URL:WAIT:done");
|
||||
}
|
||||
|
||||
if (chat == nil) {
|
||||
__block UIApplicationState as = UIApplicationStateBackground;
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
as =[[UIApplication sharedApplication] applicationState];
|
||||
});
|
||||
|
||||
if (as == UIApplicationStateActive) {
|
||||
NSLog(@"PUSH:fg->break:2");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (msgv3HashHex != nil)
|
||||
{
|
||||
OCTRealmManager *realmManager = [strongSelf.dataSource managerGetRealmManager];
|
||||
__block BOOL msgIsDelivered = NO;
|
||||
|
||||
NSLog(@"calling PUSH URL:DB check:start");
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
// HINT: only select outgoing messages (senderUniqueIdentifier == NULL)
|
||||
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"chatUniqueIdentifier == %@ AND messageText.msgv3HashHex == %@ AND senderUniqueIdentifier == nil",
|
||||
chat.uniqueIdentifier, msgv3HashHex];
|
||||
RLMResults *results = [realmManager objectsWithClass:[OCTMessageAbstract class] predicate:predicate];
|
||||
OCTMessageAbstract *message_found = [results firstObject];
|
||||
if (message_found) {
|
||||
if (message_found.messageText) {
|
||||
msgIsDelivered = message_found.messageText.isDelivered;
|
||||
}
|
||||
}
|
||||
NSLog(@"calling PUSH URL:DB check:end_real");
|
||||
});
|
||||
NSLog(@"calling PUSH URL:DB check:end");
|
||||
|
||||
if (msgIsDelivered == YES) {
|
||||
// OCTLogInfo(@"PUSH:for msgv3HashHex isDelivered=YES");
|
||||
NSLog(@"PUSH:for msgv3HashHex isDelivered=YES");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
NSLog(@"unsafe PUSH URL not allowed:-ERROR:02-");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)sendMessageToChat:(OCTChat *)chat
|
||||
text:(NSString *)text
|
||||
type:(OCTToxMessageType)type
|
||||
successBlock:(void (^)(OCTMessageAbstract *message))userSuccessBlock
|
||||
failureBlock:(void (^)(NSError *error))userFailureBlock
|
||||
{
|
||||
NSParameterAssert(chat);
|
||||
NSParameterAssert(text);
|
||||
|
||||
OCTFriend *friend = [chat.friends firstObject];
|
||||
|
||||
uint8_t *message_v3_hash_bin = calloc(1, TOX_MSGV3_MSGID_LENGTH);
|
||||
uint8_t *message_v3_hash_hexstr = calloc(1, (TOX_MSGV3_MSGID_LENGTH * 2) + 1);
|
||||
|
||||
NSString *msgv3HashHex = nil;
|
||||
UInt32 msgv3tssec = 0;
|
||||
|
||||
if ((message_v3_hash_bin) && (message_v3_hash_hexstr))
|
||||
{
|
||||
tox_messagev3_get_new_message_id(message_v3_hash_bin);
|
||||
bin_to_hex((const char *)message_v3_hash_bin, (size_t)TOX_MSGV3_MSGID_LENGTH, message_v3_hash_hexstr);
|
||||
|
||||
msgv3HashHex = [[NSString alloc] initWithBytes:message_v3_hash_hexstr length:(TOX_MSGV3_MSGID_LENGTH * 2) encoding:NSUTF8StringEncoding];
|
||||
|
||||
// HINT: set sent timestamp to now() as unixtimestamp value
|
||||
msgv3tssec = [[NSNumber numberWithDouble: [[NSDate date] timeIntervalSince1970]] integerValue];
|
||||
|
||||
free(message_v3_hash_bin);
|
||||
free(message_v3_hash_hexstr);
|
||||
}
|
||||
|
||||
__weak OCTSubmanagerChatsImpl *weakSelf = self;
|
||||
OCTSendMessageOperationSuccessBlock successBlock = ^(OCTToxMessageId messageId) {
|
||||
__strong OCTSubmanagerChatsImpl *strongSelf = weakSelf;
|
||||
|
||||
BOOL sent_push = NO;
|
||||
|
||||
if (messageId == -1) {
|
||||
if ((friend.pushToken != nil) && (friend.pushToken.length > 5)) {
|
||||
|
||||
// check push url starts with allowed values
|
||||
if (
|
||||
([friend.pushToken hasPrefix:@"https://tox.zoff.xyz/toxfcm/fcm.php?id="])
|
||||
||
|
||||
([friend.pushToken hasPrefix:@"https://gotify1.unifiedpush.org/UP?token="])
|
||||
||
|
||||
([friend.pushToken hasPrefix:@"https://ntfy.sh/"])
|
||||
) {
|
||||
sent_push = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OCTRealmManager *realmManager = [strongSelf.dataSource managerGetRealmManager];
|
||||
OCTMessageAbstract *message = [realmManager addMessageWithText:text type:type chat:chat sender:nil messageId:messageId msgv3HashHex:msgv3HashHex sentPush:sent_push tssent:msgv3tssec tsrcvd:0];
|
||||
|
||||
if (userSuccessBlock) {
|
||||
userSuccessBlock(message);
|
||||
}
|
||||
};
|
||||
|
||||
OCTSendMessageOperationFailureBlock failureBlock = ^(NSError *error) {
|
||||
__strong OCTSubmanagerChatsImpl *strongSelf = weakSelf;
|
||||
|
||||
if ((error.code == OCTToxErrorFriendSendMessageFriendNotConnected) &&
|
||||
[strongSelf.dataSource managerUseFauxOfflineMessaging]) {
|
||||
NSString *friend_pushToken = friend.pushToken;
|
||||
triggerPush(friend_pushToken, msgv3HashHex, strongSelf, chat);
|
||||
successBlock(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (userFailureBlock) {
|
||||
userFailureBlock(error);
|
||||
}
|
||||
};
|
||||
|
||||
OCTSendMessageOperation *operation = [[OCTSendMessageOperation alloc] initWithTox:[self.dataSource managerGetTox]
|
||||
friendNumber:friend.friendNumber
|
||||
messageType:type
|
||||
message:text
|
||||
msgv3HashHex:msgv3HashHex
|
||||
msgv3tssec:msgv3tssec
|
||||
successBlock:successBlock
|
||||
failureBlock:failureBlock];
|
||||
[self.sendMessageQueue addOperation:operation];
|
||||
}
|
||||
|
||||
- (BOOL)setIsTyping:(BOOL)isTyping inChat:(OCTChat *)chat error:(NSError **)error
|
||||
{
|
||||
NSParameterAssert(chat);
|
||||
|
||||
OCTFriend *friend = [chat.friends firstObject];
|
||||
OCTTox *tox = [self.dataSource managerGetTox];
|
||||
|
||||
return [tox setUserIsTyping:isTyping forFriendNumber:friend.friendNumber error:error];
|
||||
}
|
||||
|
||||
#pragma mark - NSNotification
|
||||
|
||||
- (void)friendConnectionStatusChangeNotification:(NSNotification *)notification
|
||||
{
|
||||
OCTFriend *friend = notification.object;
|
||||
|
||||
if (! friend) {
|
||||
OCTLogWarn(@"no friend received in notification %@, exiting", notification);
|
||||
return;
|
||||
}
|
||||
|
||||
if (friend.isConnected) {
|
||||
[self resendUndeliveredMessagesToFriend:friend];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)resendUndeliveredMessagesToFriend:(OCTFriend *)friend
|
||||
{
|
||||
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
|
||||
|
||||
OCTChat *chat = [realmManager getOrCreateChatWithFriend:friend];
|
||||
|
||||
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"chatUniqueIdentifier == %@"
|
||||
@" AND senderUniqueIdentifier == nil"
|
||||
@" AND messageText.isDelivered == NO",
|
||||
chat.uniqueIdentifier];
|
||||
|
||||
RLMResults *results = [realmManager objectsWithClass:[OCTMessageAbstract class] predicate:predicate];
|
||||
|
||||
for (OCTMessageAbstract *message in results) {
|
||||
OCTLogInfo(@"Resending message to friend %@", friend);
|
||||
|
||||
__weak OCTSubmanagerChatsImpl *weakSelf = self;
|
||||
OCTSendMessageOperationSuccessBlock successBlock = ^(OCTToxMessageId messageId) {
|
||||
__strong OCTSubmanagerChatsImpl *strongSelf = weakSelf;
|
||||
|
||||
OCTRealmManager *realmManager = [strongSelf.dataSource managerGetRealmManager];
|
||||
|
||||
[realmManager updateObject:message withBlock:^(OCTMessageAbstract *theMessage) {
|
||||
theMessage.messageText.messageId = messageId;
|
||||
}];
|
||||
};
|
||||
|
||||
OCTSendMessageOperationFailureBlock failureBlock = ^(NSError *error) {
|
||||
OCTLogWarn(@"Cannot resend message to friend %@, error %@", friend, error);
|
||||
};
|
||||
|
||||
OCTSendMessageOperation *operation = [[OCTSendMessageOperation alloc] initWithTox:[self.dataSource managerGetTox]
|
||||
friendNumber:friend.friendNumber
|
||||
messageType:message.messageText.type
|
||||
message:message.messageText.text
|
||||
msgv3HashHex:message.messageText.msgv3HashHex
|
||||
msgv3tssec:message.tssent
|
||||
successBlock:successBlock
|
||||
failureBlock:failureBlock];
|
||||
[self.sendMessageQueue addOperation:operation];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - OCTToxDelegate
|
||||
|
||||
/*
|
||||
* send mesgV3 high level ACK message.
|
||||
*/
|
||||
- (void)tox:(OCTTox *)tox sendFriendHighlevelACK:(NSString *)message
|
||||
friendNumber:(OCTToxFriendNumber)friendNumber
|
||||
msgv3HashHex:(NSString *)msgv3HashHex
|
||||
sendTimestamp:(uint32_t)sendTimestamp
|
||||
{
|
||||
OCTSendMessageOperation *operation = [[OCTSendMessageOperation alloc] initWithTox:[self.dataSource managerGetTox]
|
||||
friendNumber:friendNumber
|
||||
messageType:OCTToxMessageTypeHighlevelack
|
||||
message:message
|
||||
msgv3HashHex:msgv3HashHex
|
||||
msgv3tssec:sendTimestamp
|
||||
successBlock:nil
|
||||
failureBlock:nil];
|
||||
[self.sendMessageQueue addOperation:operation];
|
||||
}
|
||||
|
||||
/*
|
||||
* Process incoming text message from friend.
|
||||
*/
|
||||
- (void)tox:(OCTTox *)tox friendMessage:(NSString *)message
|
||||
type:(OCTToxMessageType)type
|
||||
friendNumber:(OCTToxFriendNumber)friendNumber
|
||||
msgv3HashHex:(NSString *)msgv3HashHex
|
||||
sendTimestamp:(uint32_t)sendTimestamp
|
||||
{
|
||||
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
|
||||
|
||||
NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil];
|
||||
OCTFriend *friend = [realmManager friendWithPublicKey:publicKey];
|
||||
OCTChat *chat = [realmManager getOrCreateChatWithFriend:friend];
|
||||
|
||||
if (msgv3HashHex != nil)
|
||||
{
|
||||
// HINT: check for double message, but only select incoming messages (senderUniqueIdentifier != NULL)
|
||||
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"chatUniqueIdentifier == %@ AND messageText.msgv3HashHex == %@ AND senderUniqueIdentifier != nil",
|
||||
chat.uniqueIdentifier, msgv3HashHex];
|
||||
RLMResults *results = [realmManager objectsWithClass:[OCTMessageAbstract class] predicate:predicate];
|
||||
OCTMessageAbstract *message_found = [results firstObject];
|
||||
|
||||
if (message_found) {
|
||||
OCTLogInfo(@"friendMessage ignoring double message i %@", chat.uniqueIdentifier);
|
||||
OCTLogInfo(@"friendMessage ignoring double message f %@", friend);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[realmManager addMessageWithText:message type:type chat:chat sender:friend messageId:0 msgv3HashHex:msgv3HashHex sentPush:NO tssent:sendTimestamp tsrcvd:0];
|
||||
}
|
||||
|
||||
- (void)tox:(OCTTox *)tox friendHighLevelACK:(NSString *)message
|
||||
friendNumber:(OCTToxFriendNumber)friendNumber
|
||||
msgv3HashHex:(NSString *)msgv3HashHex
|
||||
sendTimestamp:(uint32_t)sendTimestamp
|
||||
{
|
||||
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
|
||||
|
||||
NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil];
|
||||
OCTFriend *friend = [realmManager friendWithPublicKey:publicKey];
|
||||
OCTChat *chat = [realmManager getOrCreateChatWithFriend:friend];
|
||||
|
||||
// HINT: only select outgoing messages
|
||||
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"chatUniqueIdentifier == %@ AND messageText.msgv3HashHex == %@ AND senderUniqueIdentifier == nil",
|
||||
chat.uniqueIdentifier, msgv3HashHex];
|
||||
|
||||
// HINT: we still sort and use only 1 result row, just in case more than 1 row is returned.
|
||||
// but if more than 1 row is returned that would actually be an error.
|
||||
// we use the newest message with this Hash
|
||||
RLMResults *results = [realmManager objectsWithClass:[OCTMessageAbstract class] predicate:predicate];
|
||||
results = [results sortedResultsUsingKeyPath:@"dateInterval" ascending:YES];
|
||||
|
||||
OCTMessageAbstract *message_found = [results firstObject];
|
||||
|
||||
if (! message_found) {
|
||||
return;
|
||||
}
|
||||
|
||||
OCTLogInfo(@"friendHighLevelACK recevied from friend %@", friend);
|
||||
|
||||
[realmManager updateObject:message_found withBlock:^(OCTMessageAbstract *theMessage) {
|
||||
theMessage.messageText.isDelivered = YES;
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
- (void)tox:(OCTTox *)tox messageDelivered:(OCTToxMessageId)messageId friendNumber:(OCTToxFriendNumber)friendNumber
|
||||
{
|
||||
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
|
||||
|
||||
NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil];
|
||||
OCTFriend *friend = [realmManager friendWithPublicKey:publicKey];
|
||||
|
||||
if (friend.msgv3Capability == YES)
|
||||
{
|
||||
// HINT: if friend has msgV3 capability, we ignore the low level ACK and keep waiting for the high level ACK
|
||||
OCTLogInfo(@"messageDelivered ignoring low level ACK %@", friend);
|
||||
return;
|
||||
}
|
||||
|
||||
OCTChat *chat = [realmManager getOrCreateChatWithFriend:friend];
|
||||
|
||||
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"chatUniqueIdentifier == %@ AND messageText.messageId == %d",
|
||||
chat.uniqueIdentifier, messageId];
|
||||
|
||||
// messageId is reset on every launch, so we want to update delivered status on latest message.
|
||||
RLMResults *results = [realmManager objectsWithClass:[OCTMessageAbstract class] predicate:predicate];
|
||||
results = [results sortedResultsUsingKeyPath:@"dateInterval" ascending:NO];
|
||||
|
||||
OCTMessageAbstract *message = [results firstObject];
|
||||
|
||||
if (! message) {
|
||||
return;
|
||||
}
|
||||
|
||||
[realmManager updateObject:message withBlock:^(OCTMessageAbstract *theMessage) {
|
||||
theMessage.messageText.isDelivered = YES;
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
@ -0,0 +1,46 @@
|
||||
// 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>
|
||||
|
||||
@class OCTTox;
|
||||
@class OCTRealmManager;
|
||||
@protocol OCTFileStorageProtocol;
|
||||
|
||||
/**
|
||||
* Notification is send when connection status of friend has changed.
|
||||
*
|
||||
* - object OCTFriend whose status has changed.
|
||||
* - userInfo nil
|
||||
*/
|
||||
static NSString *const kOCTFriendConnectionStatusChangeNotification = @"kOCTFriendConnectionStatusChangeNotification";
|
||||
|
||||
/**
|
||||
* Notification is send on user avatar update.
|
||||
*
|
||||
* - object nil
|
||||
* - userInfo nil
|
||||
*/
|
||||
static NSString *const kOCTUserAvatarWasUpdatedNotification = @"kOCTUserAvatarWasUpdatedNotification";
|
||||
|
||||
/**
|
||||
* Send this notifications to schedule cleanup of uploaded/downloaded files. All files without OCTMessageFile
|
||||
* will be removed.
|
||||
*
|
||||
* - object nil
|
||||
* - userInfo nil
|
||||
*/
|
||||
static NSString *const kOCTScheduleFileTransferCleanupNotification = @"kOCTScheduleFileTransferCleanupNotification";
|
||||
|
||||
@protocol OCTSubmanagerDataSource <NSObject>
|
||||
|
||||
- (OCTTox *)managerGetTox;
|
||||
- (BOOL)managerIsToxConnected;
|
||||
- (void)managerSaveTox;
|
||||
- (OCTRealmManager *)managerGetRealmManager;
|
||||
- (id<OCTFileStorageProtocol>)managerGetFileStorage;
|
||||
- (NSNotificationCenter *)managerGetNotificationCenter;
|
||||
- (BOOL)managerUseFauxOfflineMessaging;
|
||||
|
||||
@end
|
@ -0,0 +1,10 @@
|
||||
// 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 "OCTSubmanagerFiles.h"
|
||||
#import "OCTSubmanagerProtocol.h"
|
||||
|
||||
@interface OCTSubmanagerFilesImpl : NSObject <OCTSubmanagerFiles, OCTSubmanagerProtocol>
|
||||
|
||||
@end
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,10 @@
|
||||
// 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 "OCTSubmanagerFriends.h"
|
||||
#import "OCTSubmanagerProtocol.h"
|
||||
|
||||
@interface OCTSubmanagerFriendsImpl : NSObject <OCTSubmanagerFriends, OCTSubmanagerProtocol>
|
||||
|
||||
@end
|
@ -0,0 +1,361 @@
|
||||
// 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 "OCTSubmanagerFriendsImpl.h"
|
||||
#import "OCTLogging.h"
|
||||
#import "OCTTox.h"
|
||||
#import "OCTFriend.h"
|
||||
#import "OCTFriendRequest.h"
|
||||
#import "OCTRealmManager.h"
|
||||
#import "Firebase.h"
|
||||
|
||||
@implementation OCTSubmanagerFriendsImpl
|
||||
@synthesize dataSource = _dataSource;
|
||||
|
||||
#pragma mark - Public
|
||||
|
||||
- (BOOL)sendFriendRequestToAddress:(NSString *)address message:(NSString *)message error:(NSError **)error
|
||||
{
|
||||
NSParameterAssert(address);
|
||||
NSParameterAssert(message);
|
||||
|
||||
OCTTox *tox = [self.dataSource managerGetTox];
|
||||
|
||||
OCTToxFriendNumber friendNumber = [tox addFriendWithAddress:address message:message error:error];
|
||||
|
||||
if (friendNumber == kOCTToxFriendNumberFailure) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
[self.dataSource managerSaveTox];
|
||||
|
||||
return [self createFriendWithFriendNumber:friendNumber error:error];
|
||||
}
|
||||
|
||||
- (BOOL)approveFriendRequest:(OCTFriendRequest *)friendRequest error:(NSError **)error
|
||||
{
|
||||
NSParameterAssert(friendRequest);
|
||||
|
||||
OCTTox *tox = [self.dataSource managerGetTox];
|
||||
|
||||
OCTToxFriendNumber friendNumber = [tox addFriendWithNoRequestWithPublicKey:friendRequest.publicKey error:error];
|
||||
|
||||
if (friendNumber == kOCTToxFriendNumberFailure) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
[self.dataSource managerSaveTox];
|
||||
|
||||
[[self.dataSource managerGetRealmManager] deleteObject:friendRequest];
|
||||
|
||||
return [self createFriendWithFriendNumber:friendNumber error:error];
|
||||
}
|
||||
|
||||
- (void)removeFriendRequest:(OCTFriendRequest *)friendRequest
|
||||
{
|
||||
NSParameterAssert(friendRequest);
|
||||
|
||||
[[self.dataSource managerGetRealmManager] deleteObject:friendRequest];
|
||||
}
|
||||
|
||||
- (BOOL)removeFriend:(OCTFriend *)friend error:(NSError **)error
|
||||
{
|
||||
NSParameterAssert(friend);
|
||||
|
||||
OCTTox *tox = [self.dataSource managerGetTox];
|
||||
|
||||
if (! [tox deleteFriendWithFriendNumber:friend.friendNumber error:error]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
[self.dataSource managerSaveTox];
|
||||
|
||||
[[self.dataSource managerGetRealmManager] deleteObject:friend];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - Private category
|
||||
|
||||
- (void)configure
|
||||
{
|
||||
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
|
||||
OCTTox *tox = [self.dataSource managerGetTox];
|
||||
|
||||
[realmManager updateObjectsWithClass:[OCTFriend class] predicate:nil updateBlock:^(OCTFriend *friend) {
|
||||
// Tox may change friendNumber after relaunch, resetting them.
|
||||
friend.friendNumber = kOCTToxFriendNumberFailure;
|
||||
}];
|
||||
|
||||
for (NSNumber *friendNumber in [tox friendsArray]) {
|
||||
OCTToxFriendNumber number = [friendNumber intValue];
|
||||
NSError *error;
|
||||
|
||||
NSString *publicKey = [tox publicKeyFromFriendNumber:number error:&error];
|
||||
|
||||
if (! publicKey) {
|
||||
@throw [NSException exceptionWithName:@"Cannot find publicKey for existing friendNumber, Tox save data is broken"
|
||||
reason:error.debugDescription
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"publicKey == %@", publicKey];
|
||||
RLMResults *results = [realmManager objectsWithClass:[OCTFriend class] predicate:predicate];
|
||||
|
||||
if (results.count == 0) {
|
||||
// It seems that friend is in Tox but isn't in Realm. Let's add it.
|
||||
[self createFriendWithFriendNumber:number error:nil];
|
||||
continue;
|
||||
}
|
||||
|
||||
OCTFriend *friend = [results firstObject];
|
||||
|
||||
// Reset some fields for friends.
|
||||
[realmManager updateObject:friend withBlock:^(OCTFriend *theFriend) {
|
||||
theFriend.friendNumber = number;
|
||||
theFriend.status = OCTToxUserStatusNone;
|
||||
theFriend.isConnected = NO;
|
||||
theFriend.connectionStatus = OCTToxConnectionStatusNone;
|
||||
theFriend.isTyping = NO;
|
||||
NSDate *dateOffline = [tox friendGetLastOnlineWithFriendNumber:number error:nil];
|
||||
theFriend.lastSeenOnlineInterval = [dateOffline timeIntervalSince1970];
|
||||
}];
|
||||
}
|
||||
|
||||
// Remove all OCTFriend's which aren't bounded to tox. User cannot interact with them anyway.
|
||||
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"friendNumber == %d", kOCTToxFriendNumberFailure];
|
||||
RLMResults *results = [realmManager objectsWithClass:[OCTFriend class] predicate:predicate];
|
||||
|
||||
for (OCTFriend *friend in results) {
|
||||
[realmManager deleteObject:friend];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - OCTToxDelegate
|
||||
|
||||
- (void)tox:(OCTTox *)tox friendRequestWithMessage:(NSString *)message publicKey:(NSString *)publicKey
|
||||
{
|
||||
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
|
||||
|
||||
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"publicKey == %@", publicKey];
|
||||
RLMResults *results = [realmManager objectsWithClass:[OCTFriendRequest class] predicate:predicate];
|
||||
if (results.count > 0) {
|
||||
// friendRequest already exists
|
||||
return;
|
||||
}
|
||||
|
||||
results = [realmManager objectsWithClass:[OCTFriend class] predicate:predicate];
|
||||
if (results.count > 0) {
|
||||
// friend with such publicKey already exists
|
||||
return;
|
||||
}
|
||||
|
||||
OCTFriendRequest *request = [OCTFriendRequest new];
|
||||
request.publicKey = publicKey;
|
||||
request.message = message;
|
||||
request.dateInterval = [[NSDate date] timeIntervalSince1970];
|
||||
|
||||
[realmManager addObject:request];
|
||||
}
|
||||
|
||||
- (void)tox:(OCTTox *)tox friendNameUpdate:(NSString *)name friendNumber:(OCTToxFriendNumber)friendNumber
|
||||
{
|
||||
[self.dataSource managerSaveTox];
|
||||
|
||||
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
|
||||
|
||||
NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil];
|
||||
OCTFriend *friend = [realmManager friendWithPublicKey:publicKey];
|
||||
|
||||
[realmManager updateObject:friend withBlock:^(OCTFriend *theFriend) {
|
||||
theFriend.name = name;
|
||||
|
||||
if (name.length && [theFriend.nickname isEqualToString:theFriend.publicKey]) {
|
||||
theFriend.nickname = name;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)tox:(OCTTox *)tox friendPushTokenUpdate:(NSString *)pushToken friendNumber:(OCTToxFriendNumber)friendNumber
|
||||
{
|
||||
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
|
||||
|
||||
NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil];
|
||||
OCTFriend *friend = [realmManager friendWithPublicKey:publicKey];
|
||||
|
||||
[realmManager updateObject:friend withBlock:^(OCTFriend *theFriend) {
|
||||
theFriend.pushToken = pushToken;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)tox:(OCTTox *)tox friendStatusMessageUpdate:(NSString *)statusMessage friendNumber:(OCTToxFriendNumber)friendNumber
|
||||
{
|
||||
[self.dataSource managerSaveTox];
|
||||
|
||||
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
|
||||
NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil];
|
||||
OCTFriend *friend = [realmManager friendWithPublicKey:publicKey];
|
||||
|
||||
[realmManager updateObject:friend withBlock:^(OCTFriend *theFriend) {
|
||||
theFriend.statusMessage = statusMessage;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)tox:(OCTTox *)tox friendStatusUpdate:(OCTToxUserStatus)status friendNumber:(OCTToxFriendNumber)friendNumber
|
||||
{
|
||||
[self.dataSource managerSaveTox];
|
||||
|
||||
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
|
||||
NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil];
|
||||
OCTFriend *friend = [realmManager friendWithPublicKey:publicKey];
|
||||
|
||||
[realmManager updateObject:friend withBlock:^(OCTFriend *theFriend) {
|
||||
theFriend.status = status;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)tox:(OCTTox *)tox friendIsTypingUpdate:(BOOL)isTyping friendNumber:(OCTToxFriendNumber)friendNumber
|
||||
{
|
||||
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
|
||||
NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil];
|
||||
OCTFriend *friend = [realmManager friendWithPublicKey:publicKey];
|
||||
|
||||
[realmManager updateObject:friend withBlock:^(OCTFriend *theFriend) {
|
||||
theFriend.isTyping = isTyping;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)tox:(OCTTox *)tox friendSetMsgv3Capability:(BOOL)msgv3Capability friendNumber:(OCTToxFriendNumber)friendNumber
|
||||
{
|
||||
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
|
||||
NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil];
|
||||
OCTFriend *friend = [realmManager friendWithPublicKey:publicKey];
|
||||
|
||||
if (friend.msgv3Capability != msgv3Capability)
|
||||
{
|
||||
[realmManager updateObject:friend withBlock:^(OCTFriend *theFriend) {
|
||||
theFriend.msgv3Capability = msgv3Capability;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)tox:(OCTTox *)tox friendConnectionStatusChanged:(OCTToxConnectionStatus)status friendNumber:(OCTToxFriendNumber)friendNumber
|
||||
{
|
||||
[self.dataSource managerSaveTox];
|
||||
|
||||
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
|
||||
NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil];
|
||||
OCTFriend *friend = [realmManager friendWithPublicKey:publicKey];
|
||||
|
||||
[realmManager updateObject:friend withBlock:^(OCTFriend *theFriend) {
|
||||
|
||||
if ((status != OCTToxConnectionStatusNone)
|
||||
&& (theFriend.connectionStatus == OCTToxConnectionStatusNone))
|
||||
{
|
||||
// Friend is coming online now
|
||||
|
||||
OCTToxCapabilities f_caps = [tox friendGetCapabilitiesWithFriendNumber:friendNumber];
|
||||
OCTLogVerbose(@"f_caps=%lu", f_caps);
|
||||
NSString* cap_string = [NSString stringWithFormat:@"%lu", f_caps];
|
||||
theFriend.capabilities2 = cap_string;
|
||||
|
||||
NSString *token = [FIRMessaging messaging].FCMToken;
|
||||
if (token.length > 0)
|
||||
{
|
||||
// HINT: prepend a dummy "A" char as placeholder for Tox Packet ID.
|
||||
// it will be replaced in sendLosslessPacketWithFriendNumber by pktid
|
||||
NSString *data = [NSString stringWithFormat:@"Ahttps://tox.zoff.xyz/toxfcm/fcm.php?id=%@&type=1", token];
|
||||
// NSLog(@"token push url=%@", data);
|
||||
NSError *error;
|
||||
|
||||
// HINT: pktid 181 is for sending push urls to friends
|
||||
BOOL result = [tox sendLosslessPacketWithFriendNumber:friendNumber
|
||||
pktid:181
|
||||
data:data
|
||||
error:&error];
|
||||
}
|
||||
}
|
||||
theFriend.isConnected = (status != OCTToxConnectionStatusNone);
|
||||
theFriend.connectionStatus = status;
|
||||
|
||||
if (! theFriend.isConnected) {
|
||||
// Friend is offline now
|
||||
NSDate *dateOffline = [tox friendGetLastOnlineWithFriendNumber:friendNumber error:nil];
|
||||
NSTimeInterval timeSince = [dateOffline timeIntervalSince1970];
|
||||
theFriend.lastSeenOnlineInterval = timeSince;
|
||||
}
|
||||
}];
|
||||
|
||||
[[self.dataSource managerGetNotificationCenter] postNotificationName:kOCTFriendConnectionStatusChangeNotification object:friend];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (BOOL)createFriendWithFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)userError
|
||||
{
|
||||
OCTTox *tox = [self.dataSource managerGetTox];
|
||||
NSError *error;
|
||||
|
||||
OCTFriend *friend = [OCTFriend new];
|
||||
|
||||
friend.friendNumber = friendNumber;
|
||||
|
||||
friend.publicKey = [tox publicKeyFromFriendNumber:friendNumber error:&error];
|
||||
if ([self checkForError:error andAssignTo:userError]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
friend.name = [tox friendNameWithFriendNumber:friendNumber error:&error];
|
||||
if ([self checkForError:error andAssignTo:userError]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
friend.statusMessage = [tox friendStatusMessageWithFriendNumber:friendNumber error:&error];
|
||||
if ([self checkForError:error andAssignTo:userError]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
friend.status = [tox friendStatusWithFriendNumber:friendNumber error:&error];
|
||||
if ([self checkForError:error andAssignTo:userError]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
friend.connectionStatus = [tox friendConnectionStatusWithFriendNumber:friendNumber error:&error];
|
||||
if ([self checkForError:error andAssignTo:userError]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSDate *lastSeenOnline = [tox friendGetLastOnlineWithFriendNumber:friendNumber error:&error];
|
||||
friend.lastSeenOnlineInterval = [lastSeenOnline timeIntervalSince1970];
|
||||
if ([self checkForError:error andAssignTo:userError]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
friend.isTyping = [tox isFriendTypingWithFriendNumber:friendNumber error:&error];
|
||||
if ([self checkForError:error andAssignTo:userError]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
friend.isConnected = (friend.connectionStatus != OCTToxConnectionStatusNone);
|
||||
friend.nickname = friend.name.length ? friend.name : friend.publicKey;
|
||||
|
||||
[[self.dataSource managerGetRealmManager] addObject:friend];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)checkForError:(NSError *)toCheck andAssignTo:(NSError **)toAssign
|
||||
{
|
||||
if (! toCheck) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (toAssign) {
|
||||
*toAssign = toCheck;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
@ -0,0 +1,10 @@
|
||||
// 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 "OCTSubmanagerObjects.h"
|
||||
#import "OCTSubmanagerProtocol.h"
|
||||
|
||||
@interface OCTSubmanagerObjectsImpl : NSObject <OCTSubmanagerObjects, OCTSubmanagerProtocol>
|
||||
|
||||
@end
|
@ -0,0 +1,103 @@
|
||||
// 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 "OCTSubmanagerObjectsImpl.h"
|
||||
#import "OCTRealmManager.h"
|
||||
#import "OCTFriend.h"
|
||||
#import "OCTFriendRequest.h"
|
||||
#import "OCTChat.h"
|
||||
#import "OCTCall.h"
|
||||
#import "OCTMessageAbstract.h"
|
||||
#import "OCTSettingsStorageObject.h"
|
||||
|
||||
@implementation OCTSubmanagerObjectsImpl
|
||||
@synthesize dataSource = _dataSource;
|
||||
|
||||
#pragma mark - Public
|
||||
|
||||
- (void)setGenericSettingsData:(NSData *)data
|
||||
{
|
||||
OCTRealmManager *manager = [self.dataSource managerGetRealmManager];
|
||||
|
||||
[manager updateObject:manager.settingsStorage withBlock:^(OCTSettingsStorageObject *object) {
|
||||
object.genericSettingsData = data;
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSData *)genericSettingsData
|
||||
{
|
||||
OCTRealmManager *manager = [self.dataSource managerGetRealmManager];
|
||||
return manager.settingsStorage.genericSettingsData;
|
||||
}
|
||||
|
||||
- (RLMResults *)objectsForType:(OCTFetchRequestType)type predicate:(NSPredicate *)predicate
|
||||
{
|
||||
OCTRealmManager *manager = [self.dataSource managerGetRealmManager];
|
||||
return [manager objectsWithClass:[self classForFetchRequestType:type] predicate:predicate];
|
||||
}
|
||||
|
||||
- (OCTObject *)objectWithUniqueIdentifier:(NSString *)uniqueIdentifier forType:(OCTFetchRequestType)type
|
||||
{
|
||||
OCTRealmManager *manager = [self.dataSource managerGetRealmManager];
|
||||
return [manager objectWithUniqueIdentifier:uniqueIdentifier class:[self classForFetchRequestType:type]];
|
||||
}
|
||||
|
||||
#pragma mark - Friends
|
||||
|
||||
- (void)changeFriend:(OCTFriend *)friend nickname:(NSString *)nickname
|
||||
{
|
||||
OCTRealmManager *manager = [self.dataSource managerGetRealmManager];
|
||||
|
||||
[manager updateObject:friend withBlock:^(OCTFriend *theFriend) {
|
||||
if (nickname.length) {
|
||||
theFriend.nickname = nickname;
|
||||
}
|
||||
else if (theFriend.name.length) {
|
||||
theFriend.nickname = theFriend.name;
|
||||
}
|
||||
else {
|
||||
theFriend.nickname = theFriend.publicKey;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Chats
|
||||
|
||||
- (void)changeChat:(OCTChat *)chat enteredText:(NSString *)enteredText
|
||||
{
|
||||
OCTRealmManager *manager = [self.dataSource managerGetRealmManager];
|
||||
|
||||
[manager updateObject:chat withBlock:^(OCTChat *theChat) {
|
||||
theChat.enteredText = enteredText;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)changeChat:(OCTChat *)chat lastReadDateInterval:(NSTimeInterval)lastReadDateInterval
|
||||
{
|
||||
OCTRealmManager *manager = [self.dataSource managerGetRealmManager];
|
||||
|
||||
[manager updateObject:chat withBlock:^(OCTChat *theChat) {
|
||||
theChat.lastReadDateInterval = lastReadDateInterval;
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (Class)classForFetchRequestType:(OCTFetchRequestType)type
|
||||
{
|
||||
switch (type) {
|
||||
case OCTFetchRequestTypeFriend:
|
||||
return [OCTFriend class];
|
||||
case OCTFetchRequestTypeFriendRequest:
|
||||
return [OCTFriendRequest class];
|
||||
case OCTFetchRequestTypeChat:
|
||||
return [OCTChat class];
|
||||
case OCTFetchRequestTypeCall:
|
||||
return [OCTCall class];
|
||||
case OCTFetchRequestTypeMessageAbstract:
|
||||
return [OCTMessageAbstract class];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
@ -0,0 +1,16 @@
|
||||
// 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 "OCTSubmanagerDataSource.h"
|
||||
#import "OCTToxDelegate.h"
|
||||
|
||||
@protocol OCTSubmanagerProtocol <OCTToxDelegate>
|
||||
|
||||
@property (weak, nonatomic) id<OCTSubmanagerDataSource> dataSource;
|
||||
|
||||
@optional
|
||||
|
||||
- (void)configure;
|
||||
|
||||
@end
|
@ -0,0 +1,10 @@
|
||||
// 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 "OCTSubmanagerUser.h"
|
||||
#import "OCTSubmanagerProtocol.h"
|
||||
|
||||
@interface OCTSubmanagerUserImpl : NSObject <OCTSubmanagerUser, OCTSubmanagerProtocol>
|
||||
|
||||
@end
|
@ -0,0 +1,132 @@
|
||||
// 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 "OCTSubmanagerUserImpl.h"
|
||||
#import "OCTTox.h"
|
||||
#import "OCTManagerConstants.h"
|
||||
#import "OCTRealmManager.h"
|
||||
#import "OCTSettingsStorageObject.h"
|
||||
|
||||
@implementation OCTSubmanagerUserImpl
|
||||
@synthesize delegate = _delegate;
|
||||
@synthesize dataSource = _dataSource;
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (OCTToxConnectionStatus)connectionStatus
|
||||
{
|
||||
return [self.dataSource managerGetTox].connectionStatus;
|
||||
}
|
||||
|
||||
- (NSString *)userAddress
|
||||
{
|
||||
return [self.dataSource managerGetTox].userAddress;
|
||||
}
|
||||
|
||||
- (OCTToxCapabilities)capabilities
|
||||
{
|
||||
return [self.dataSource managerGetTox].capabilities;
|
||||
}
|
||||
|
||||
- (NSString *)publicKey
|
||||
{
|
||||
return [self.dataSource managerGetTox].publicKey;
|
||||
}
|
||||
|
||||
#pragma mark - Public
|
||||
|
||||
- (OCTToxNoSpam)nospam
|
||||
{
|
||||
return [self.dataSource managerGetTox].nospam;
|
||||
}
|
||||
|
||||
- (void)setNospam:(OCTToxNoSpam)nospam
|
||||
{
|
||||
[self.dataSource managerGetTox].nospam = nospam;
|
||||
[self.dataSource managerSaveTox];
|
||||
}
|
||||
|
||||
- (OCTToxUserStatus)userStatus
|
||||
{
|
||||
return [self.dataSource managerGetTox].userStatus;
|
||||
}
|
||||
|
||||
- (void)setUserStatus:(OCTToxUserStatus)userStatus
|
||||
{
|
||||
[self.dataSource managerGetTox].userStatus = userStatus;
|
||||
[self.dataSource managerSaveTox];
|
||||
}
|
||||
|
||||
- (BOOL)setUserName:(NSString *)name error:(NSError **)error
|
||||
{
|
||||
if ([[self.dataSource managerGetTox] setNickname:name error:error]) {
|
||||
[self.dataSource managerSaveTox];
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSString *)userName
|
||||
{
|
||||
return [[self.dataSource managerGetTox] userName];
|
||||
}
|
||||
|
||||
- (BOOL)setUserStatusMessage:(NSString *)statusMessage error:(NSError **)error
|
||||
{
|
||||
if ([[self.dataSource managerGetTox] setUserStatusMessage:statusMessage error:error]) {
|
||||
[self.dataSource managerSaveTox];
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSString *)userStatusMessage
|
||||
{
|
||||
return [[self.dataSource managerGetTox] userStatusMessage];
|
||||
}
|
||||
|
||||
- (BOOL)setUserAvatar:(NSData *)avatar error:(NSError **)error
|
||||
{
|
||||
if (avatar && (avatar.length > kOCTManagerMaxAvatarSize)) {
|
||||
if (error) {
|
||||
*error = [NSError errorWithDomain:kOCTManagerErrorDomain
|
||||
code:OCTSetUserAvatarErrorTooBig
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey : @"Cannot set user avatar",
|
||||
NSLocalizedFailureReasonErrorKey : @"Avatar is too big",
|
||||
}];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
OCTRealmManager *realmManager = self.dataSource.managerGetRealmManager;
|
||||
|
||||
[realmManager updateObject:realmManager.settingsStorage withBlock:^(OCTSettingsStorageObject *object) {
|
||||
object.userAvatarData = avatar;
|
||||
}];
|
||||
|
||||
[self.dataSource.managerGetNotificationCenter postNotificationName:kOCTUserAvatarWasUpdatedNotification object:nil];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSData *)userAvatar
|
||||
{
|
||||
return self.dataSource.managerGetRealmManager.settingsStorage.userAvatarData;
|
||||
}
|
||||
|
||||
#pragma mark - OCTToxDelegate
|
||||
|
||||
- (void)tox:(OCTTox *)tox connectionStatus:(OCTToxConnectionStatus)connectionStatus
|
||||
{
|
||||
if (connectionStatus != OCTToxConnectionStatusNone) {
|
||||
[self.dataSource managerSaveTox];
|
||||
}
|
||||
|
||||
[self.delegate submanagerUser:self connectionStatusUpdate:connectionStatus];
|
||||
}
|
||||
|
||||
@end
|
Reference in New Issue
Block a user