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,33 @@
// 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 "OCTAudioEngine.h"
#import "TPCircularBuffer.h"
@import AVFoundation;
extern int kBufferLength;
extern int kNumberOfChannels;
extern int kDefaultSampleRate;
extern int kSampleCount_incoming_audio;
extern int kSampleCount_outgoing_audio;
extern int kBitsPerByte;
extern int kFramesPerPacket;
extern int kBytesPerSample;
extern int kNumberOfAudioQueueBuffers;
@class OCTAudioQueue;
@interface OCTAudioEngine ()
#if ! TARGET_OS_IPHONE
@property (strong, nonatomic, readonly) NSString *inputDeviceID;
@property (strong, nonatomic, readonly) NSString *outputDeviceID;
#endif
@property (nonatomic, strong) OCTAudioQueue *outputQueue;
@property (nonatomic, strong) OCTAudioQueue *inputQueue;
- (void)makeQueues:(NSError **)error;
@end

View File

@ -0,0 +1,92 @@
// 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 "OCTToxAV.h"
@interface OCTAudioEngine : NSObject
@property (weak, nonatomic) OCTToxAV *toxav;
@property (nonatomic, assign) OCTToxFriendNumber friendNumber;
/**
* YES to send audio frames over to tox, otherwise NO.
* Default is YES.
*/
@property (nonatomic, assign) BOOL enableMicrophone;
/**
* Starts the Audio Processing Graph.
* @param error Pointer to error object.
* @return YES on success, otherwise NO.
*/
- (BOOL)startAudioFlow:(NSError **)error;
/**
* Stops the Audio Processing Graph.
* @param error Pointer to error object.
* @return YES on success, otherwise NO.
*/
- (BOOL)stopAudioFlow:(NSError **)error;
/**
* Checks if the Audio Graph is processing.
* @param error Pointer to error object.
* @return YES if Audio Graph is running, otherwise NO.
*/
- (BOOL)isAudioRunning:(NSError **)error;
/**
* Provide audio data that will be placed in buffer to be played in speaker.
* @param pcm An array of audio samples (sample_count * channels elements).
* @param sampleCount The number of audio samples per channel in the PCM array.
* @param channels Number of audio channels.
* @param sampleRate Sampling rate used in this frame.
*/
- (void)provideAudioFrames:(OCTToxAVPCMData *)pcm sampleCount:(OCTToxAVSampleCount)sampleCount channels:(OCTToxAVChannels)channels sampleRate:(OCTToxAVSampleRate)sampleRate fromFriend:(OCTToxFriendNumber)friendNumber;
@end
#if ! TARGET_OS_IPHONE
@interface OCTAudioEngine (MacDevice)
/**
* Set the input device (not available on iOS).
* @param inputDeviceID Core Audio's unique ID for the device. See
* public OCTSubmanagerCalls.h for what these should be.
* @param error If this method returns NO, contains more information on the
* underlying error.
* @return YES on success, otherwise NO.
*/
- (BOOL)setInputDeviceID:(NSString *)inputDeviceID error:(NSError **)error;
/**
* Set the output device (not available on iOS).
* @param outputDeviceID Core Audio's unique ID for the device. See
* public OCTSubmanagerCalls.h for what these should be.
* @param error If this method returns NO, contains more information on the
* underlying error.
* @return YES on success, otherwise NO.
*/
- (BOOL)setOutputDeviceID:(NSString *)outputDeviceID error:(NSError **)error;
@end
#else
@interface OCTAudioEngine (iOSDevice)
/**
* Switch the output to/from the device's speaker.
* @param speaker Whether we should use the speaker for output.
* @param error If this method returns NO, contains more information on the
* underlying error.
* @return YES on success, otherwise NO.
*/
- (BOOL)routeAudioToSpeaker:(BOOL)speaker error:(NSError **)error;
@end
#endif

View File

@ -0,0 +1,191 @@
// 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 "OCTAudioEngine+Private.h"
#import "OCTToxAV+Private.h"
#import "OCTAudioQueue.h"
#import "OCTLogging.h"
@import AVFoundation;
@interface OCTAudioEngine ()
@property (nonatomic, assign) OCTToxAVSampleRate outputSampleRate;
@property (nonatomic, assign) OCTToxAVChannels outputNumberOfChannels;
@end
@implementation OCTAudioEngine
#pragma mark - LifeCycle
- (instancetype)init
{
self = [super init];
if (! self) {
return nil;
}
_outputSampleRate = kDefaultSampleRate;
_outputNumberOfChannels = kNumberOfChannels;
_enableMicrophone = YES;
return self;
}
#pragma mark - SPI
#if ! TARGET_OS_IPHONE
- (BOOL)setInputDeviceID:(NSString *)inputDeviceID error:(NSError **)error
{
// if audio is not active, we can't really be bothered to check that the
// device exists; we rely on startAudioFlow: to fail later.
if (! self.inputQueue) {
_inputDeviceID = inputDeviceID;
return YES;
}
if ([self.inputQueue setDeviceID:inputDeviceID error:error]) {
_inputDeviceID = inputDeviceID;
return YES;
}
return NO;
}
- (BOOL)setOutputDeviceID:(NSString *)outputDeviceID error:(NSError **)error
{
if (! self.outputQueue) {
_outputDeviceID = outputDeviceID;
return YES;
}
if ([self.outputQueue setDeviceID:outputDeviceID error:error]) {
_outputDeviceID = outputDeviceID;
return YES;
}
return NO;
}
#else
- (BOOL)routeAudioToSpeaker:(BOOL)speaker error:(NSError **)error
{
AVAudioSession *session = [AVAudioSession sharedInstance];
AVAudioSessionPortOverride override;
if (speaker) {
override = AVAudioSessionPortOverrideSpeaker;
}
else {
override = AVAudioSessionPortOverrideNone;
}
return [session overrideOutputAudioPort:override error:error];
}
#endif
- (BOOL)startAudioFlow:(NSError **)error
{
#if TARGET_OS_IPHONE
AVAudioSession *session = [AVAudioSession sharedInstance];
if (! ([session setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionAllowBluetooth error:error] &&
[session setPreferredSampleRate:kDefaultSampleRate error:error] &&
[session setMode:AVAudioSessionModeVoiceChat error:error] &&
[session setActive:YES error:error])) {
return NO;
}
#endif
[self makeQueues:error];
if (! (self.outputQueue && self.inputQueue)) {
return NO;
}
OCTAudioEngine *__weak welf = self;
self.inputQueue.sendDataBlock = ^(void *data, OCTToxAVSampleCount samples, OCTToxAVSampleRate rate, OCTToxAVChannels channelCount) {
OCTAudioEngine *aoi = welf;
if (aoi.enableMicrophone) {
// TOXAUDIO: -outgoing-audio-
[aoi.toxav sendAudioFrame:data
sampleCount:samples
channels:channelCount
sampleRate:rate
toFriend:aoi.friendNumber
error:nil];
}
};
// TOXAUDIO: -incoming-audio-
[self.outputQueue updateSampleRate:self.outputSampleRate numberOfChannels:self.outputNumberOfChannels error:nil];
if (! [self.inputQueue begin:error] || ! [self.outputQueue begin:error]) {
return NO;
}
return YES;
}
- (BOOL)stopAudioFlow:(NSError **)error
{
if (! [self.inputQueue stop:error] || ! [self.outputQueue stop:error]) {
return NO;
}
#if TARGET_OS_IPHONE
AVAudioSession *session = [AVAudioSession sharedInstance];
BOOL ret = [session setActive:NO error:error];
#else
BOOL ret = YES;
#endif
self.inputQueue = nil;
self.outputQueue = nil;
return ret;
}
- (void)provideAudioFrames:(OCTToxAVPCMData *)pcm sampleCount:(OCTToxAVSampleCount)sampleCount channels:(OCTToxAVChannels)channels sampleRate:(OCTToxAVSampleRate)sampleRate fromFriend:(OCTToxFriendNumber)friendNumber
{
// TOXAUDIO: -incoming-audio-
int32_t len = (int32_t)(channels * sampleCount * sizeof(OCTToxAVPCMData));
TPCircularBuffer *buf = [self.outputQueue getBufferPointer];
if (buf) {
TPCircularBufferProduceBytes(buf, pcm, len);
}
if ((self.outputSampleRate != sampleRate) || (self.outputNumberOfChannels != channels)) {
// failure is logged by OCTAudioQueue.
[self.outputQueue updateSampleRate:sampleRate numberOfChannels:channels error:nil];
self.outputSampleRate = sampleRate;
self.outputNumberOfChannels = channels;
}
}
- (BOOL)isAudioRunning:(NSError **)error
{
return self.inputQueue.running && self.outputQueue.running;
}
- (void)makeQueues:(NSError **)error
{
// Note: OCTAudioQueue handles the case where the device ids are nil - in that case
// we don't set the device explicitly, and the default is used.
#if TARGET_OS_IPHONE
// TOXAUDIO: -incoming-audio-
self.outputQueue = [[OCTAudioQueue alloc] initWithOutputDeviceID:nil error:error];
// TOXAUDIO: -outgoing-audio-
self.inputQueue = [[OCTAudioQueue alloc] initWithInputDeviceID:nil error:error];
#else
self.outputQueue = [[OCTAudioQueue alloc] initWithOutputDeviceID:self.outputDeviceID error:error];
self.inputQueue = [[OCTAudioQueue alloc] initWithInputDeviceID:self.inputDeviceID error:error];
#endif
}
@end

View File

@ -0,0 +1,74 @@
// 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 "TPCircularBuffer.h"
@import AudioToolbox;
#pragma mark - C declarations
extern OSStatus (*_AudioQueueAllocateBuffer)(AudioQueueRef inAQ,
UInt32 inBufferByteSize,
AudioQueueBufferRef *outBuffer);
extern OSStatus (*_AudioQueueDispose)(AudioQueueRef inAQ,
Boolean inImmediate);
extern OSStatus (*_AudioQueueEnqueueBuffer)(AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer,
UInt32 inNumPacketDescs,
const AudioStreamPacketDescription *inPacketDescs);
extern OSStatus (*_AudioQueueFreeBuffer)(AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer);
extern OSStatus (*_AudioQueueNewInput)(const AudioStreamBasicDescription *inFormat,
AudioQueueInputCallback inCallbackProc,
void *inUserData,
CFRunLoopRef inCallbackRunLoop,
CFStringRef inCallbackRunLoopMode,
UInt32 inFlags,
AudioQueueRef *outAQ);
extern OSStatus (*_AudioQueueNewOutput)(const AudioStreamBasicDescription *inFormat,
AudioQueueOutputCallback inCallbackProc,
void *inUserData,
CFRunLoopRef inCallbackRunLoop,
CFStringRef inCallbackRunLoopMode,
UInt32 inFlags,
AudioQueueRef *outAQ);
extern OSStatus (*_AudioQueueSetProperty)(AudioQueueRef inAQ,
AudioQueuePropertyID inID,
const void *inData,
UInt32 inDataSize);
extern OSStatus (*_AudioQueueStart)(AudioQueueRef inAQ,
const AudioTimeStamp *inStartTime);
extern OSStatus (*_AudioQueueStop)(AudioQueueRef inAQ,
Boolean inImmediate);
#if ! TARGET_OS_IPHONE
extern OSStatus (*_AudioObjectGetPropertyData)(AudioObjectID inObjectID,
const AudioObjectPropertyAddress *inAddress,
UInt32 inQualifierDataSize,
const void *inQualifierData,
UInt32 *ioDataSize,
void *outData);
#endif
/* no idea what to name this thing, so here it is */
@interface OCTAudioQueue : NSObject
@property (strong, nonatomic, readonly) NSString *deviceID;
@property (copy, nonatomic) void (^sendDataBlock)(void *, OCTToxAVSampleCount, OCTToxAVSampleRate, OCTToxAVChannels);
@property (assign, nonatomic, readonly) BOOL running;
- (instancetype)initWithInputDeviceID:(NSString *)devID error:(NSError **)error;
- (instancetype)initWithOutputDeviceID:(NSString *)devID error:(NSError **)error;
- (TPCircularBuffer *)getBufferPointer;
- (BOOL)updateSampleRate:(OCTToxAVSampleRate)sampleRate numberOfChannels:(OCTToxAVChannels)numberOfChannels error:(NSError **)err;
#if ! TARGET_OS_IPHONE
- (BOOL)setDeviceID:(NSString *)deviceID error:(NSError **)err;
#endif
- (BOOL)begin:(NSError **)error;
- (BOOL)stop:(NSError **)error;
@end

View File

@ -0,0 +1,425 @@
// 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 "OCTToxAV.h"
#import "OCTAudioQueue.h"
#import "TPCircularBuffer.h"
#import "OCTLogging.h"
@import AVFoundation;
@import AudioToolbox;
const int kBufferLength = 384000;
const int kNumberOfChannels = 1;
const int kDefaultSampleRate = 48000;
const int kSampleCount_incoming_audio = 1920;
const int kSampleCount_outgoing_audio = (1920 / 2);
const int kBitsPerByte = 8;
const int kFramesPerPacket = 1;
// if you make this too small, the output queue will silently not play,
// but you will still get fill callbacks; it's really weird
const int kFramesPerOutputBuffer_incoming_audio = kSampleCount_incoming_audio / 4;
const int kFramesPerOutputBuffer_outgoing_audio = kSampleCount_outgoing_audio / 4;
const int kBytesPerSample = sizeof(SInt16);
const int kNumberOfAudioQueueBuffers = 8;
OSStatus (*_AudioQueueAllocateBuffer)(AudioQueueRef inAQ,
UInt32 inBufferByteSize,
AudioQueueBufferRef *outBuffer) = AudioQueueAllocateBuffer;
OSStatus (*_AudioQueueDispose)(AudioQueueRef inAQ,
Boolean inImmediate) = AudioQueueDispose;
OSStatus (*_AudioQueueEnqueueBuffer)(AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer,
UInt32 inNumPacketDescs,
const AudioStreamPacketDescription *inPacketDescs) = AudioQueueEnqueueBuffer;
OSStatus (*_AudioQueueFreeBuffer)(AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer) = AudioQueueFreeBuffer;
OSStatus (*_AudioQueueNewInput)(const AudioStreamBasicDescription *inFormat,
AudioQueueInputCallback inCallbackProc,
void *inUserData,
CFRunLoopRef inCallbackRunLoop,
CFStringRef inCallbackRunLoopMode,
UInt32 inFlags,
AudioQueueRef *outAQ) = AudioQueueNewInput;
OSStatus (*_AudioQueueNewOutput)(const AudioStreamBasicDescription *inFormat,
AudioQueueOutputCallback inCallbackProc,
void *inUserData,
CFRunLoopRef inCallbackRunLoop,
CFStringRef inCallbackRunLoopMode,
UInt32 inFlags,
AudioQueueRef *outAQ) = AudioQueueNewOutput;
OSStatus (*_AudioQueueSetProperty)(AudioQueueRef inAQ,
AudioQueuePropertyID inID,
const void *inData,
UInt32 inDataSize) = AudioQueueSetProperty;
OSStatus (*_AudioQueueStart)(AudioQueueRef inAQ,
const AudioTimeStamp *inStartTime) = AudioQueueStart;
OSStatus (*_AudioQueueStop)(AudioQueueRef inAQ,
Boolean inImmediate) = AudioQueueStop;
#if ! TARGET_OS_IPHONE
OSStatus (*_AudioObjectGetPropertyData)(AudioObjectID inObjectID,
const AudioObjectPropertyAddress *inAddress,
UInt32 inQualifierDataSize,
const void *inQualifierData,
UInt32 *ioDataSize,
void *outData) = AudioObjectGetPropertyData;
#endif
static NSError *OCTErrorFromCoreAudioCode(OSStatus resultCode)
{
return [NSError errorWithDomain:NSOSStatusErrorDomain
code:resultCode
userInfo:@{NSLocalizedDescriptionKey : @"Consult the CoreAudio header files/google for the meaning of the error code."}];
}
#if ! TARGET_OS_IPHONE
static NSString *OCTGetSystemAudioDevice(AudioObjectPropertySelector sel, NSError **err)
{
AudioDeviceID devID = 0;
OSStatus ok = 0;
UInt32 size = sizeof(AudioDeviceID);
AudioObjectPropertyAddress address = {
.mSelector = sel,
.mScope = kAudioObjectPropertyScopeGlobal,
.mElement = kAudioObjectPropertyElementMaster
};
ok = _AudioObjectGetPropertyData(kAudioObjectSystemObject, &address, 0, NULL, &size, &devID);
if (ok != kAudioHardwareNoError) {
OCTLogCCError(@"failed AudioObjectGetPropertyData for system object: %d! Crash may or may not be imminent", ok);
if (err) {
*err = OCTErrorFromCoreAudioCode(ok);
}
return nil;
}
address.mSelector = kAudioDevicePropertyDeviceUID;
CFStringRef unique = NULL;
size = sizeof(unique);
ok = _AudioObjectGetPropertyData(devID, &address, 0, NULL, &size, &unique);
if (ok != kAudioHardwareNoError) {
OCTLogCCError(@"failed AudioObjectGetPropertyData for selected device: %d! Crash may or may not be imminent", ok);
if (err) {
*err = OCTErrorFromCoreAudioCode(ok);
}
return nil;
}
return (__bridge NSString *)unique;
}
#endif
@interface OCTAudioQueue ()
// use this to track what nil means in terms of audio device
@property (assign, nonatomic) BOOL isOutput;
@property (assign, nonatomic) AudioStreamBasicDescription streamFmt;
@property (assign, nonatomic) AudioQueueRef audioQueue;
@property (assign, nonatomic) TPCircularBuffer buffer;
@property (assign, nonatomic) BOOL running;
@end
@implementation OCTAudioQueue
{
AudioQueueBufferRef _AQBuffers[kNumberOfAudioQueueBuffers];
}
- (instancetype)initWithDeviceID:(NSString *)devID isOutput:(BOOL)output error:(NSError **)error
{
#if TARGET_OS_IPHONE
AVAudioSession *session = [AVAudioSession sharedInstance];
_streamFmt.mSampleRate = session.sampleRate;
#else
_streamFmt.mSampleRate = kDefaultSampleRate;
#endif
_streamFmt.mFormatID = kAudioFormatLinearPCM;
_streamFmt.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
_streamFmt.mChannelsPerFrame = kNumberOfChannels;
_streamFmt.mBytesPerFrame = kBytesPerSample * kNumberOfChannels;
_streamFmt.mBitsPerChannel = kBitsPerByte * kBytesPerSample;
_streamFmt.mFramesPerPacket = kFramesPerPacket;
_streamFmt.mBytesPerPacket = kBytesPerSample * kNumberOfChannels * kFramesPerPacket;
_isOutput = output;
_deviceID = devID;
TPCircularBufferInit(&_buffer, kBufferLength);
OSStatus res = [self createAudioQueue];
if (res != 0) {
if (error) {
*error = OCTErrorFromCoreAudioCode(res);
}
return nil;
}
return self;
}
- (instancetype)initWithInputDeviceID:(NSString *)devID error:(NSError **)error
{
// TOXAUDIO: -outgoing-audio-
return [self initWithDeviceID:devID isOutput:NO error:error];
}
- (instancetype)initWithOutputDeviceID:(NSString *)devID error:(NSError **)error
{
// TOXAUDIO: -incoming-audio-
return [self initWithDeviceID:devID isOutput:YES error:error];
}
- (void)dealloc
{
if (self.running) {
[self stop:nil];
}
if (self.audioQueue) {
_AudioQueueDispose(self.audioQueue, true);
}
TPCircularBufferCleanup(&_buffer);
}
- (OSStatus)createAudioQueue
{
OSStatus err;
if (self.isOutput) {
// TOXAUDIO: -incoming-audio-
err = _AudioQueueNewOutput(&_streamFmt, (void *)&FillOutputBuffer, (__bridge void *)self, NULL, kCFRunLoopCommonModes, 0, &_audioQueue);
}
else {
// TOXAUDIO: -outgoing-audio-
err = _AudioQueueNewInput(&_streamFmt, (void *)&InputAvailable, (__bridge void *)self, NULL, kCFRunLoopCommonModes, 0, &_audioQueue);
}
if (err != 0) {
return err;
}
if (_deviceID) {
err = _AudioQueueSetProperty(self.audioQueue, kAudioQueueProperty_CurrentDevice, &_deviceID, sizeof(CFStringRef));
}
return err;
}
- (BOOL)begin:(NSError **)error
{
OCTLogVerbose(@"begin");
if (! self.audioQueue) {
OSStatus res = [self createAudioQueue];
if (res != 0) {
OCTLogError(@"Can't create the audio queue again after a presumably failed updateSampleRate:... call.");
if (error) {
*error = OCTErrorFromCoreAudioCode(res);
}
return NO;
}
}
for (int i = 0; i < kNumberOfAudioQueueBuffers; ++i) {
if (self.isOutput) {
// TOXAUDIO: -incoming-audio-
_AudioQueueAllocateBuffer(self.audioQueue, kBytesPerSample * kNumberOfChannels * kFramesPerOutputBuffer_incoming_audio, &(_AQBuffers[i]));
} else {
// TOXAUDIO: -outgoing-audio-
_AudioQueueAllocateBuffer(self.audioQueue, kBytesPerSample * kNumberOfChannels * kFramesPerOutputBuffer_outgoing_audio, &(_AQBuffers[i]));
}
_AudioQueueEnqueueBuffer(self.audioQueue, _AQBuffers[i], 0, NULL);
if (self.isOutput) {
// TOXAUDIO: -outgoing-audio-
// For some reason we have to fill it with zero or the callback never gets called.
FillOutputBuffer(self, self.audioQueue, _AQBuffers[i]);
}
}
OCTLogVerbose(@"Allocated buffers; starting now!");
OSStatus res = _AudioQueueStart(self.audioQueue, NULL);
if (res != 0) {
if (error) {
*error = OCTErrorFromCoreAudioCode(res);
}
return NO;
}
self.running = YES;
return YES;
}
- (BOOL)stop:(NSError **)error
{
OCTLogVerbose(@"stop");
OSStatus res = _AudioQueueStop(self.audioQueue, true);
if (res != 0) {
if (error) {
*error = OCTErrorFromCoreAudioCode(res);
}
return NO;
}
for (int i = 0; i < kNumberOfAudioQueueBuffers; ++i) {
_AudioQueueFreeBuffer(self.audioQueue, _AQBuffers[i]);
}
OCTLogVerbose(@"Freed buffers");
self.running = NO;
return YES;
}
- (TPCircularBuffer *)getBufferPointer
{
return &_buffer;
}
- (BOOL)setDeviceID:(NSString *)deviceID error:(NSError **)err
{
#if ! TARGET_OS_IPHONE
if (deviceID == nil) {
OCTLogVerbose(@"using the default device because nil passed to OCTAudioQueue setDeviceID:");
deviceID = OCTGetSystemAudioDevice(self.isOutput ?
kAudioHardwarePropertyDefaultOutputDevice :
kAudioHardwarePropertyDefaultInputDevice, err);
if (! deviceID) {
return NO;
}
}
BOOL needToRestart = self.running;
// we need to pause the queue for a sec
if (needToRestart && ! [self stop:err]) {
return NO;
}
OSStatus ok = _AudioQueueSetProperty(self.audioQueue, kAudioQueueProperty_CurrentDevice, &deviceID, sizeof(CFStringRef));
if (ok != 0) {
OCTLogError(@"setDeviceID: Error while live setting device to '%@': %d", deviceID, ok);
if (err) {
*err = OCTErrorFromCoreAudioCode(ok);
}
}
else {
_deviceID = deviceID;
OCTLogVerbose(@"Successfully set the device id to %@", deviceID);
}
if ((needToRestart && ! [self begin:err]) || (ok != 0)) {
return NO;
}
else {
return YES;
}
#else
return NO;
#endif
}
- (BOOL)updateSampleRate:(OCTToxAVSampleRate)sampleRate numberOfChannels:(OCTToxAVChannels)numberOfChannels error:(NSError **)err
{
OCTLogVerbose(@"updateSampleRate %u, %u", sampleRate, (unsigned int)numberOfChannels);
BOOL needToRestart = self.running;
if (needToRestart && ! [self stop:err]) {
return NO;
}
AudioQueueRef aq = self.audioQueue;
self.audioQueue = nil;
_AudioQueueDispose(aq, true);
_streamFmt.mSampleRate = sampleRate;
_streamFmt.mChannelsPerFrame = numberOfChannels;
_streamFmt.mBytesPerFrame = kBytesPerSample * numberOfChannels;
_streamFmt.mBytesPerPacket = kBytesPerSample * numberOfChannels * kFramesPerPacket;
OSStatus res = [self createAudioQueue];
if (res != 0) {
OCTLogError(@"oops, could not recreate the audio queue: %d after samplerate/nc change. enjoy your overflowing buffer", (int)res);
if (err) {
*err = OCTErrorFromCoreAudioCode(res);
}
return NO;
}
else if (needToRestart) {
return [self begin:err];
}
else {
return YES;
}
}
// avoid annoying bridge cast in 1st param!
static void InputAvailable(OCTAudioQueue *__unsafe_unretained context,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer,
const AudioTimeStamp *inStartTime,
UInt32 inNumPackets,
const AudioStreamPacketDescription *inPacketDesc)
{
TPCircularBufferProduceBytes(&(context->_buffer),
inBuffer->mAudioData,
inBuffer->mAudioDataByteSize);
int32_t availableBytesToConsume;
void *tail = TPCircularBufferTail(&context->_buffer, &availableBytesToConsume);
// TOXAUDIO: -outgoing-audio-
int32_t minimalBytesToConsume = kSampleCount_outgoing_audio * kNumberOfChannels * sizeof(SInt16);
if (context.isOutput) {
// TOXAUDIO: -incoming-audio-
minimalBytesToConsume = kSampleCount_incoming_audio * kNumberOfChannels * sizeof(SInt16);
}
int32_t cyclesToConsume = availableBytesToConsume / minimalBytesToConsume;
for (int32_t i = 0; i < cyclesToConsume; i++) {
if (context.isOutput) {
// TOXAUDIO: -incoming-audio-
context.sendDataBlock(tail, kSampleCount_incoming_audio, context.streamFmt.mSampleRate, kNumberOfChannels);
} else {
// TOXAUDIO: -outgoing-audio-
context.sendDataBlock(tail, kSampleCount_outgoing_audio, context.streamFmt.mSampleRate, kNumberOfChannels);
}
TPCircularBufferConsume(&context->_buffer, minimalBytesToConsume);
tail = TPCircularBufferTail(&context->_buffer, &availableBytesToConsume);
}
_AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
}
static void FillOutputBuffer(OCTAudioQueue *__unsafe_unretained context,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer)
{
int32_t targetBufferSize = inBuffer->mAudioDataBytesCapacity;
SInt16 *targetBuffer = inBuffer->mAudioData;
int32_t availableBytes;
SInt16 *buffer = TPCircularBufferTail(&context->_buffer, &availableBytes);
if (buffer) {
uint32_t cpy = MIN(availableBytes, targetBufferSize);
memcpy(targetBuffer, buffer, cpy);
TPCircularBufferConsume(&context->_buffer, cpy);
if (cpy != targetBufferSize) {
memset(targetBuffer + cpy, 0, targetBufferSize - cpy);
OCTLogCCWarn(@"warning not enough frames!!!");
}
inBuffer->mAudioDataByteSize = targetBufferSize;
}
else {
memset(targetBuffer, 0, targetBufferSize);
inBuffer->mAudioDataByteSize = targetBufferSize;
}
_AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
}
@end

View File

@ -0,0 +1,77 @@
// 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 "OCTDefaultFileStorage.h"
@interface OCTDefaultFileStorage ()
@property (copy, nonatomic) NSString *saveFileName;
@property (copy, nonatomic) NSString *baseDirectory;
@property (copy, nonatomic) NSString *temporaryDirectory;
@end
@implementation OCTDefaultFileStorage
#pragma mark - Lifecycle
- (instancetype)initWithBaseDirectory:(NSString *)baseDirectory temporaryDirectory:(NSString *)temporaryDirectory
{
return [self initWithToxSaveFileName:nil baseDirectory:baseDirectory temporaryDirectory:temporaryDirectory];
}
- (instancetype)initWithToxSaveFileName:(NSString *)saveFileName
baseDirectory:(NSString *)baseDirectory
temporaryDirectory:(NSString *)temporaryDirectory
{
self = [super init];
if (! self) {
return nil;
}
if (! saveFileName) {
saveFileName = @"save";
}
self.saveFileName = [saveFileName stringByAppendingString:@".tox"];
self.baseDirectory = baseDirectory;
self.temporaryDirectory = temporaryDirectory;
return self;
}
#pragma mark - OCTFileStorageProtocol
- (NSString *)pathForToxSaveFile
{
return [self.baseDirectory stringByAppendingPathComponent:self.saveFileName];
}
- (NSString *)pathForDatabase
{
return [self.baseDirectory stringByAppendingPathComponent:@"database"];
}
- (NSString *)pathForDatabaseEncryptionKey
{
return [self.baseDirectory stringByAppendingPathComponent:@"database.encryptionkey"];
}
- (NSString *)pathForDownloadedFilesDirectory
{
return [self.baseDirectory stringByAppendingPathComponent:@"files"];
}
- (NSString *)pathForUploadedFilesDirectory
{
return [self.baseDirectory stringByAppendingPathComponent:@"files"];
}
- (NSString *)pathForTemporaryFilesDirectory
{
return self.temporaryDirectory;
}
@end

View File

@ -0,0 +1,51 @@
// 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 "OCTManagerConfiguration.h"
#import "OCTDefaultFileStorage.h"
static NSString *const kDefaultBaseDirectory = @"me.dvor.objcTox";
@implementation OCTManagerConfiguration
#pragma mark - Class methods
+ (instancetype)defaultConfiguration
{
OCTManagerConfiguration *configuration = [OCTManagerConfiguration new];
NSString *baseDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
baseDirectory = [baseDirectory stringByAppendingPathComponent:kDefaultBaseDirectory];
[[NSFileManager defaultManager] createDirectoryAtPath:baseDirectory
withIntermediateDirectories:YES
attributes:nil
error:nil];
configuration.fileStorage = [[OCTDefaultFileStorage alloc] initWithBaseDirectory:baseDirectory
temporaryDirectory:NSTemporaryDirectory()];
configuration.options = [OCTToxOptions new];
configuration.importToxSaveFromPath = nil;
configuration.useFauxOfflineMessaging = YES;
return configuration;
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone
{
OCTManagerConfiguration *configuration = [[[self class] allocWithZone:zone] init];
configuration.fileStorage = self.fileStorage;
configuration.options = [self.options copy];
configuration.importToxSaveFromPath = [self.importToxSaveFromPath copy];
configuration.useFauxOfflineMessaging = self.useFauxOfflineMessaging;
return configuration;
}
@end

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

View File

@ -0,0 +1,31 @@
// 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"
@class OCTMessageAbstract;
@interface NSError (OCTFile)
+ (NSError *)sendFileErrorInternalError;
+ (NSError *)sendFileErrorCannotReadFile;
+ (NSError *)sendFileErrorCannotSaveFileToUploads;
+ (NSError *)sendFileErrorFriendNotFound;
+ (NSError *)sendFileErrorFriendNotConnected;
+ (NSError *)sendFileErrorNameTooLong;
+ (NSError *)sendFileErrorTooMany;
+ (NSError *)sendFileErrorFromToxFileSendError:(OCTToxErrorFileSend)code;
+ (NSError *)acceptFileErrorInternalError;
+ (NSError *)acceptFileErrorCannotWriteToFile;
+ (NSError *)acceptFileErrorFriendNotFound;
+ (NSError *)acceptFileErrorFriendNotConnected;
+ (NSError *)acceptFileErrorWrongMessage:(OCTMessageAbstract *)message;
+ (NSError *)acceptFileErrorFromToxFileSendChunkError:(OCTToxErrorFileSendChunk)code;
+ (NSError *)acceptFileErrorFromToxFileControl:(OCTToxErrorFileControl)code;
+ (NSError *)fileTransferErrorWrongMessage:(OCTMessageAbstract *)message;
@end

View File

@ -0,0 +1,189 @@
// 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 "NSError+OCTFile.h"
#import "OCTManagerConstants.h"
@implementation NSError (OCTFile)
+ (NSError *)sendFileErrorInternalError
{
return [NSError errorWithDomain:kOCTManagerErrorDomain
code:OCTSendFileErrorInternalError
userInfo:@{
NSLocalizedDescriptionKey : @"Send file",
NSLocalizedFailureReasonErrorKey : @"Internal error",
}];
}
+ (NSError *)sendFileErrorCannotReadFile
{
return [NSError errorWithDomain:kOCTManagerErrorDomain
code:OCTSendFileErrorCannotReadFile
userInfo:@{
NSLocalizedDescriptionKey : @"Send file",
NSLocalizedFailureReasonErrorKey : @"Cannot read file",
}];
}
+ (NSError *)sendFileErrorCannotSaveFileToUploads
{
return [NSError errorWithDomain:kOCTManagerErrorDomain
code:OCTSendFileErrorCannotSaveFileToUploads
userInfo:@{
NSLocalizedDescriptionKey : @"Send file",
NSLocalizedFailureReasonErrorKey : @"Cannot save send file to uploads folder.",
}];
}
+ (NSError *)sendFileErrorFriendNotFound
{
return [NSError errorWithDomain:kOCTManagerErrorDomain
code:OCTSendFileErrorFriendNotFound
userInfo:@{
NSLocalizedDescriptionKey : @"Send file",
NSLocalizedFailureReasonErrorKey : @"Friend to send file to was not found.",
}];
}
+ (NSError *)sendFileErrorFriendNotConnected
{
return [NSError errorWithDomain:kOCTManagerErrorDomain
code:OCTSendFileErrorFriendNotConnected
userInfo:@{
NSLocalizedDescriptionKey : @"Send file",
NSLocalizedFailureReasonErrorKey : @"Friend is not connected at the moment.",
}];
}
+ (NSError *)sendFileErrorNameTooLong
{
return [NSError errorWithDomain:kOCTManagerErrorDomain
code:OCTSendFileErrorFriendNotConnected
userInfo:@{
NSLocalizedDescriptionKey : @"Send file",
NSLocalizedFailureReasonErrorKey : @"File name is too long.",
}];
}
+ (NSError *)sendFileErrorTooMany
{
return [NSError errorWithDomain:kOCTManagerErrorDomain
code:OCTSendFileErrorFriendNotConnected
userInfo:@{
NSLocalizedDescriptionKey : @"Send file",
NSLocalizedFailureReasonErrorKey : @"Too many active file transfers.",
}];
}
+ (NSError *)sendFileErrorFromToxFileSendError:(OCTToxErrorFileSend)code
{
switch (code) {
case OCTToxErrorFileSendUnknown:
return [self sendFileErrorInternalError];
case OCTToxErrorFileSendFriendNotFound:
return [self sendFileErrorFriendNotFound];
case OCTToxErrorFileSendFriendNotConnected:
return [self sendFileErrorFriendNotConnected];
case OCTToxErrorFileSendNameTooLong:
return [self sendFileErrorNameTooLong];
case OCTToxErrorFileSendTooMany:
return [self sendFileErrorTooMany];
}
}
+ (NSError *)acceptFileErrorInternalError
{
return [NSError errorWithDomain:kOCTManagerErrorDomain
code:OCTAcceptFileErrorInternalError
userInfo:@{
NSLocalizedDescriptionKey : @"Download file",
NSLocalizedFailureReasonErrorKey : @"Internal error",
}];
}
+ (NSError *)acceptFileErrorCannotWriteToFile
{
return [NSError errorWithDomain:kOCTManagerErrorDomain
code:OCTAcceptFileErrorCannotWriteToFile
userInfo:@{
NSLocalizedDescriptionKey : @"Download file",
NSLocalizedFailureReasonErrorKey : @"File is not available for writing.",
}];
}
+ (NSError *)acceptFileErrorFriendNotFound
{
return [NSError errorWithDomain:kOCTManagerErrorDomain
code:OCTAcceptFileErrorFriendNotFound
userInfo:@{
NSLocalizedDescriptionKey : @"Download file",
NSLocalizedFailureReasonErrorKey : @"Friend to send file to was not found.",
}];
}
+ (NSError *)acceptFileErrorFriendNotConnected
{
return [NSError errorWithDomain:kOCTManagerErrorDomain
code:OCTAcceptFileErrorFriendNotConnected
userInfo:@{
NSLocalizedDescriptionKey : @"Download file",
NSLocalizedFailureReasonErrorKey : @"Friend is not connected at the moment.",
}];
}
+ (NSError *)acceptFileErrorWrongMessage:(OCTMessageAbstract *)message
{
return [NSError errorWithDomain:kOCTManagerErrorDomain
code:OCTAcceptFileErrorWrongMessage
userInfo:@{
NSLocalizedDescriptionKey : @"Download file",
NSLocalizedFailureReasonErrorKey : [NSString stringWithFormat:@"Specified wrong message %@", message],
}];
}
+ (NSError *)acceptFileErrorFromToxFileSendChunkError:(OCTToxErrorFileSendChunk)code
{
switch (code) {
case OCTToxErrorFileSendChunkFriendNotFound:
return [self acceptFileErrorFriendNotFound];
case OCTToxErrorFileSendChunkFriendNotConnected:
return [self acceptFileErrorFriendNotConnected];
case OCTToxErrorFileSendChunkUnknown:
case OCTToxErrorFileSendChunkNotFound:
case OCTToxErrorFileSendChunkNotTransferring:
case OCTToxErrorFileSendChunkInvalidLength:
case OCTToxErrorFileSendChunkSendq:
case OCTToxErrorFileSendChunkWrongPosition:
return [self acceptFileErrorInternalError];
}
}
+ (NSError *)acceptFileErrorFromToxFileControl:(OCTToxErrorFileControl)code
{
switch (code) {
case OCTToxErrorFileControlFriendNotFound:
return [self acceptFileErrorFriendNotFound];
case OCTToxErrorFileControlFriendNotConnected:
return [self acceptFileErrorFriendNotConnected];
case OCTToxErrorFileControlNotFound:
case OCTToxErrorFileControlNotPaused:
case OCTToxErrorFileControlDenied:
case OCTToxErrorFileControlAlreadyPaused:
case OCTToxErrorFileControlSendq:
return [self acceptFileErrorInternalError];
}
}
+ (NSError *)fileTransferErrorWrongMessage:(OCTMessageAbstract *)message
{
return [NSError errorWithDomain:kOCTManagerErrorDomain
code:OCTFileTransferErrorWrongMessage
userInfo:@{
NSLocalizedDescriptionKey : @"Error",
NSLocalizedFailureReasonErrorKey : [NSString stringWithFormat:@"Specified wrong message %@", message],
}];
}
@end

View File

@ -0,0 +1,42 @@
// 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 "OCTFileBaseOperation.h"
@interface OCTFileBaseOperation (Private)
@property (weak, nonatomic, readonly, nullable) OCTTox *tox;
@property (assign, nonatomic, readonly) OCTToxFriendNumber friendNumber;
@property (assign, nonatomic, readonly) OCTToxFileNumber fileNumber;
@property (assign, nonatomic, readonly) OCTToxFileSize fileSize;
/**
* Override this method to start custom actions. Call finish when operation is done.
*/
- (void)operationStarted NS_REQUIRES_SUPER;
/**
* Override this method to do clean up on operation cancellation.
*/
- (void)operationWasCanceled NS_REQUIRES_SUPER;
/**
* Call this method to change bytes done value.
*/
- (void)updateBytesDone:(OCTToxFileSize)bytesDone;
/**
* Call this method in case if operation was finished.
*/
- (void)finishWithSuccess;
/**
* Call this method in case if operation was finished or cancelled with error.
*
* @param error Pass error if occured, nil on success.
*/
- (void)finishWithError:(nonnull NSError *)error;
@end

View File

@ -0,0 +1,85 @@
// 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 "OCTTox.h"
#import "OCTToxConstants.h"
@class OCTFileBaseOperation;
/**
* Block to notify about operation progress.
*
* @param operation Operation that is running.
* @param progress Progress of operation. From 0.0 to 1.0.
* @param bytesPerSecond Speed of loading.
* @param eta Estimated time of finish of loading.
*/
typedef void (^OCTFileBaseOperationProgressBlock)(OCTFileBaseOperation *__nonnull operation);
/**
* Block to notify about operation success.
*
* @param operation Operation that is running.
* @param filePath Path of file being loaded
*/
typedef void (^OCTFileBaseOperationSuccessBlock)(OCTFileBaseOperation *__nonnull operation);
/**
* Block to notify about operation failure.
*
* @param operation Operation that is running.
*/
typedef void (^OCTFileBaseOperationFailureBlock)(OCTFileBaseOperation *__nonnull operation, NSError *__nonnull error);
@interface OCTFileBaseOperation : NSOperation
/**
* Identifier of operation, unique for all active file operations.
*/
@property (strong, nonatomic, readonly, nonnull) NSString *operationId;
/**
* Progress properties.
*/
@property (assign, nonatomic, readonly) OCTToxFileSize bytesDone;
@property (assign, nonatomic, readonly) float progress;
@property (assign, nonatomic, readonly) OCTToxFileSize bytesPerSecond;
@property (assign, nonatomic, readonly) CFTimeInterval eta;
@property (strong, nonatomic, readonly, nullable) NSDictionary *userInfo;
/**
* Creates operation id from file and friend number.
*/
+ (nonnull NSString *)operationIdFromFileNumber:(OCTToxFileNumber)fileNumber friendNumber:(OCTToxFriendNumber)friendNumber;
/**
* Create operation.
*
* @param tox Tox object to load from.
* @param friendNumber Number of friend.
* @param fileNumber Number of file to load.
* @param fileSize Size of file in bytes.
* @param userInfo Any object that will be stored by operation.
* @param progressBlock Block called to notify about loading progress. Block will be called on main thread.
* @param etaUpdateBlock Block called to notify about loading eta update. Block will be called on main thread.
* @param successBlock Block called on operation success. Block will be called on main thread.
* @param failureBlock Block called on loading error. Block will be called on main thread.
*/
- (nullable instancetype)initWithTox:(nonnull OCTTox *)tox
friendNumber:(OCTToxFriendNumber)friendNumber
fileNumber:(OCTToxFileNumber)fileNumber
fileSize:(OCTToxFileSize)fileSize
userInfo:(nullable NSDictionary *)userInfo
progressBlock:(nullable OCTFileBaseOperationProgressBlock)progressBlock
etaUpdateBlock:(nullable OCTFileBaseOperationProgressBlock)etaUpdateBlock
successBlock:(nullable OCTFileBaseOperationSuccessBlock)successBlock
failureBlock:(nullable OCTFileBaseOperationFailureBlock)failureBlock;
- (nullable instancetype)init NS_UNAVAILABLE;
+ (nullable instancetype)new NS_UNAVAILABLE;
@end

View File

@ -0,0 +1,286 @@
// 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 "OCTFileBaseOperation.h"
#import "OCTFileBaseOperation+Private.h"
#import "OCTLogging.h"
#import <QuartzCore/QuartzCore.h>
static const CFTimeInterval kMinUpdateProgressInterval = 0.1;
static const CFTimeInterval kMinUpdateEtaInterval = 1.0;
@interface OCTEtaObject : NSObject
@property (assign, nonatomic) CFTimeInterval deltaTime;
@property (assign, nonatomic) OCTToxFileSize deltaBytes;
@end
@implementation OCTEtaObject
@end
@interface OCTFileBaseOperation ()
@property (assign, atomic) BOOL privateExecuting;
@property (assign, atomic) BOOL privateFinished;
@property (weak, nonatomic, readonly, nullable) OCTTox *tox;
@property (assign, nonatomic, readonly) OCTToxFriendNumber friendNumber;
@property (assign, nonatomic, readonly) OCTToxFileNumber fileNumber;
@property (assign, nonatomic, readonly) OCTToxFileSize fileSize;
@property (assign, nonatomic, readwrite) OCTToxFileSize bytesDone;
@property (assign, nonatomic, readwrite) float progress;
@property (assign, nonatomic, readwrite) OCTToxFileSize bytesPerSecond;
@property (assign, nonatomic, readwrite) CFTimeInterval eta;
@property (copy, nonatomic) OCTFileBaseOperationProgressBlock progressBlock;
@property (copy, nonatomic) OCTFileBaseOperationProgressBlock etaUpdateBlock;
@property (copy, nonatomic) OCTFileBaseOperationSuccessBlock successBlock;
@property (copy, nonatomic) OCTFileBaseOperationFailureBlock failureBlock;
@property (assign, nonatomic) CFTimeInterval lastUpdateProgressTime;
@property (assign, nonatomic) OCTToxFileSize lastUpdateBytesDone;
@property (assign, nonatomic) CFTimeInterval lastUpdateEtaProgressTime;
@property (assign, nonatomic) OCTToxFileSize lastUpdateEtaBytesDone;
@property (strong, nonatomic) NSMutableArray *last10EtaObjects;
@end
@implementation OCTFileBaseOperation
#pragma mark - Class methods
+ (NSString *)operationIdFromFileNumber:(OCTToxFileNumber)fileNumber friendNumber:(OCTToxFriendNumber)friendNumber
{
return [NSString stringWithFormat:@"%d-%d", fileNumber, friendNumber];
}
#pragma mark - Lifecycle
- (nullable instancetype)initWithTox:(nonnull OCTTox *)tox
friendNumber:(OCTToxFriendNumber)friendNumber
fileNumber:(OCTToxFileNumber)fileNumber
fileSize:(OCTToxFileSize)fileSize
userInfo:(NSDictionary *)userInfo
progressBlock:(nullable OCTFileBaseOperationProgressBlock)progressBlock
etaUpdateBlock:(nullable OCTFileBaseOperationProgressBlock)etaUpdateBlock
successBlock:(nullable OCTFileBaseOperationSuccessBlock)successBlock
failureBlock:(nullable OCTFileBaseOperationFailureBlock)failureBlock
{
NSParameterAssert(tox);
NSParameterAssert(fileSize > 0);
self = [super init];
if (! self) {
return nil;
}
_operationId = [[self class] operationIdFromFileNumber:fileNumber friendNumber:friendNumber];
_tox = tox;
_friendNumber = friendNumber;
_fileNumber = fileNumber;
_fileSize = fileSize;
_progress = 0.0;
_bytesPerSecond = 0;
_eta = 0;
_userInfo = userInfo;
_progressBlock = [progressBlock copy];
_etaUpdateBlock = [etaUpdateBlock copy];
_successBlock = [successBlock copy];
_failureBlock = [failureBlock copy];
_bytesDone = 0;
_lastUpdateProgressTime = 0;
return self;
}
#pragma mark - Properties
- (void)setExecuting:(BOOL)executing
{
[self willChangeValueForKey:@"isExecuting"];
self.privateExecuting = executing;
[self didChangeValueForKey:@"isExecuting"];
}
- (BOOL)isExecuting
{
return self.privateExecuting;
}
- (void)setFinished:(BOOL)finished
{
[self willChangeValueForKey:@"isFinished"];
self.privateFinished = finished;
[self didChangeValueForKey:@"isFinished"];
}
- (BOOL)isFinished
{
return self.privateFinished;
}
#pragma mark - Private category
- (void)updateBytesDone:(OCTToxFileSize)bytesDone
{
self.bytesDone = bytesDone;
[self updateProgressIfNeeded:bytesDone];
[self updateEtaIfNeeded:bytesDone];
}
- (void)operationStarted
{
OCTLogInfo(@"start loading file with identifier %@", self.operationId);
}
- (void)operationWasCanceled
{
OCTLogInfo(@"was cancelled");
}
- (void)finishWithSuccess
{
OCTLogInfo(@"finished with success");
self.executing = NO;
self.finished = YES;
dispatch_async(dispatch_get_main_queue(), ^{
if (self.successBlock) {
self.successBlock(self);
}
});
}
- (void)finishWithError:(nonnull NSError *)error
{
NSParameterAssert(error);
OCTLogInfo(@"finished with error %@", error);
self.executing = NO;
self.finished = YES;
dispatch_async(dispatch_get_main_queue(), ^{
if (self.failureBlock) {
self.failureBlock(self, error);
}
});
}
#pragma mark - Override
- (void)start
{
if (self.cancelled) {
self.finished = YES;
return;
}
self.executing = YES;
self.lastUpdateProgressTime = CACurrentMediaTime();
self.lastUpdateBytesDone = 0;
self.lastUpdateEtaProgressTime = CACurrentMediaTime();
self.lastUpdateEtaBytesDone = 0;
self.last10EtaObjects = [NSMutableArray new];
[self operationStarted];
}
- (void)cancel
{
[super cancel];
[self operationWasCanceled];
self.executing = NO;
self.finished = YES;
}
- (BOOL)asynchronous
{
return YES;
}
#pragma mark - Private
- (void)updateProgressIfNeeded:(OCTToxFileSize)bytesDone
{
CFTimeInterval time = CACurrentMediaTime();
CFTimeInterval deltaTime = time - self.lastUpdateProgressTime;
if (deltaTime <= kMinUpdateProgressInterval) {
return;
}
self.lastUpdateProgressTime = time;
self.lastUpdateBytesDone = bytesDone;
self.progress = (float)bytesDone / self.fileSize;
OCTLogInfo(@"progress %.2f, bytes per second %lld, eta %.0f seconds", self.progress, self.bytesPerSecond, self.eta);
if (self.progressBlock) {
self.progressBlock(self);
}
}
- (void)updateEtaIfNeeded:(OCTToxFileSize)bytesDone
{
CFTimeInterval time = CACurrentMediaTime();
CFTimeInterval deltaTime = time - self.lastUpdateEtaProgressTime;
if (deltaTime <= kMinUpdateEtaInterval) {
return;
}
OCTToxFileSize deltaBytes = bytesDone - self.lastUpdateEtaBytesDone;
OCTToxFileSize bytesLeft = self.fileSize - bytesDone;
self.lastUpdateEtaProgressTime = time;
self.lastUpdateEtaBytesDone = bytesDone;
OCTEtaObject *etaObject = [OCTEtaObject new];
etaObject.deltaTime = deltaTime;
etaObject.deltaBytes = deltaBytes;
[self.last10EtaObjects addObject:etaObject];
if (self.last10EtaObjects.count > 10) {
[self.last10EtaObjects removeObjectAtIndex:0];
}
CFTimeInterval totalDeltaTime = 0.0;
OCTToxFileSize totalDeltaBytes = 0;
for (OCTEtaObject *object in self.last10EtaObjects) {
totalDeltaTime += object.deltaTime;
totalDeltaBytes += object.deltaBytes;
}
self.bytesPerSecond = totalDeltaBytes / totalDeltaTime;
if (totalDeltaBytes) {
self.eta = totalDeltaTime * bytesLeft / totalDeltaBytes;
}
if (self.etaUpdateBlock) {
self.etaUpdateBlock(self);
}
}
@end

View File

@ -0,0 +1,15 @@
// 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 "OCTFileInputProtocol.h"
@interface OCTFileDataInput : NSObject <OCTFileInputProtocol>
- (nullable instancetype)initWithData:(nonnull NSData *)data;
- (nullable instancetype)init NS_UNAVAILABLE;
+ (nullable instancetype)new NS_UNAVAILABLE;
@end

View File

@ -0,0 +1,50 @@
// 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 "OCTFileDataInput.h"
#import "OCTLogging.h"
@interface OCTFileDataInput ()
@property (strong, nonatomic, readonly) NSData *data;
@end
@implementation OCTFileDataInput
#pragma mark - Lifecycle
- (nullable instancetype)initWithData:(nonnull NSData *)data
{
self = [super init];
if (! self) {
return nil;
}
_data = data;
return self;
}
#pragma mark - OCTFileInputProtocol
- (BOOL)prepareToRead
{
return YES;
}
- (nonnull NSData *)bytesWithPosition:(OCTToxFileSize)position length:(size_t)length
{
@try {
return [self.data subdataWithRange:NSMakeRange((NSUInteger)position, length)];
}
@catch (NSException *ex) {
OCTLogWarn(@"catched exception %@", ex);
}
return nil;
}
@end

View File

@ -0,0 +1,15 @@
// 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 "OCTFileOutputProtocol.h"
@interface OCTFileDataOutput : NSObject <OCTFileOutputProtocol>
/**
* Result data. This property will contain data only after download finishes.
*/
@property (strong, nonatomic, readonly, nullable) NSData *resultData;
@end

View File

@ -0,0 +1,42 @@
// 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 "OCTFileDataOutput.h"
@interface OCTFileDataOutput ()
@property (strong, nonatomic) NSMutableData *tempData;
@property (strong, nonatomic) NSData *resultData;
@end
@implementation OCTFileDataOutput
#pragma mark - OCTFileOutputProtocol
- (BOOL)prepareToWrite
{
self.tempData = [NSMutableData new];
return YES;
}
- (BOOL)writeData:(nonnull NSData *)data
{
[self.tempData appendData:data];
return YES;
}
- (BOOL)finishWriting
{
self.resultData = [self.tempData copy];
self.tempData = nil;
return YES;
}
- (void)cancel
{
self.tempData = nil;
}
@end

View File

@ -0,0 +1,45 @@
// 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 "OCTFileBaseOperation.h"
@class OCTTox;
@protocol OCTFileOutputProtocol;
/**
* File operation for downloading file.
*
* When started will automatically send resume control to friend.
*/
@interface OCTFileDownloadOperation : OCTFileBaseOperation
@property (strong, nonatomic, readonly, nonnull) id<OCTFileOutputProtocol> output;
/**
* Create operation.
*
* @param fileOutput Output to use as a destination for file transfer.
*
* For other parameters description see OCTFileBaseOperation.
*/
- (nullable instancetype)initWithTox:(nonnull OCTTox *)tox
fileOutput:(nonnull id<OCTFileOutputProtocol>)fileOutput
friendNumber:(OCTToxFriendNumber)friendNumber
fileNumber:(OCTToxFileNumber)fileNumber
fileSize:(OCTToxFileSize)fileSize
userInfo:(nullable NSDictionary *)userInfo
progressBlock:(nullable OCTFileBaseOperationProgressBlock)progressBlock
etaUpdateBlock:(nullable OCTFileBaseOperationProgressBlock)etaUpdateBlock
successBlock:(nullable OCTFileBaseOperationSuccessBlock)successBlock
failureBlock:(nullable OCTFileBaseOperationFailureBlock)failureBlock;
/**
* Call this method to get next chunk to operation.
*
* @param chunk Next chunk of data to append to file.
* @param position Position in file to append chunk.
*/
- (void)receiveChunk:(nullable NSData *)chunk position:(OCTToxFileSize)position;
@end

View File

@ -0,0 +1,111 @@
// 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 "OCTFileDownloadOperation.h"
#import "OCTFileBaseOperation+Private.h"
#import "OCTFileOutputProtocol.h"
#import "OCTLogging.h"
#import "NSError+OCTFile.h"
@interface OCTFileDownloadOperation ()
@end
@implementation OCTFileDownloadOperation
#pragma mark - Lifecycle
- (nullable instancetype)initWithTox:(nonnull OCTTox *)tox
fileOutput:(nonnull id<OCTFileOutputProtocol>)fileOutput
friendNumber:(OCTToxFriendNumber)friendNumber
fileNumber:(OCTToxFileNumber)fileNumber
fileSize:(OCTToxFileSize)fileSize
userInfo:(NSDictionary *)userInfo
progressBlock:(nullable OCTFileBaseOperationProgressBlock)progressBlock
etaUpdateBlock:(nullable OCTFileBaseOperationProgressBlock)etaUpdateBlock
successBlock:(nullable OCTFileBaseOperationSuccessBlock)successBlock
failureBlock:(nullable OCTFileBaseOperationFailureBlock)failureBlock
{
NSParameterAssert(fileOutput);
self = [super initWithTox:tox
friendNumber:friendNumber
fileNumber:fileNumber
fileSize:fileSize
userInfo:userInfo
progressBlock:progressBlock
etaUpdateBlock:etaUpdateBlock
successBlock:successBlock
failureBlock:failureBlock];
if (! self) {
return nil;
}
_output = fileOutput;
return self;
}
#pragma mark - Public
- (void)receiveChunk:(NSData *)chunk position:(OCTToxFileSize)position
{
if (! chunk) {
if ([self.output finishWriting]) {
[self finishWithSuccess];
}
else {
[self finishWithError:[NSError acceptFileErrorCannotWriteToFile]];
}
return;
}
if (self.bytesDone != position) {
OCTLogWarn(@"bytesDone doesn't match position");
[self.tox fileSendControlForFileNumber:self.fileNumber
friendNumber:self.friendNumber
control:OCTToxFileControlCancel
error:nil];
[self finishWithError:[NSError acceptFileErrorInternalError]];
return;
}
if (! [self.output writeData:chunk]) {
[self finishWithError:[NSError acceptFileErrorCannotWriteToFile]];
return;
}
[self updateBytesDone:self.bytesDone + chunk.length];
}
#pragma mark - Override
- (void)operationStarted
{
[super operationStarted];
if (! [self.output prepareToWrite]) {
[self finishWithError:[NSError acceptFileErrorCannotWriteToFile]];
}
NSError *error;
if (! [self.tox fileSendControlForFileNumber:self.fileNumber
friendNumber:self.friendNumber
control:OCTToxFileControlResume
error:&error]) {
OCTLogWarn(@"cannot send control %@", error);
[self finishWithError:[NSError acceptFileErrorFromToxFileControl:error.code]];
return;
}
}
- (void)operationWasCanceled
{
[super operationWasCanceled];
[self.output cancel];
}
@end

View File

@ -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 <Foundation/Foundation.h>
#import "OCTToxConstants.h"
@protocol OCTFileInputProtocol <NSObject>
/**
* Prepare input to read. This method will be called before first call to bytesWithPosition:length:.
*
* @return YES on success, NO on failure.
*/
- (BOOL)prepareToRead;
/**
* Provide bytes.
*
* @param position Start position to start reading from.
* @param length Length of bytes to read.
*
* @return NSData on success, nil on failure
*/
- (nonnull NSData *)bytesWithPosition:(OCTToxFileSize)position length:(size_t)length;
@end

View File

@ -0,0 +1,37 @@
// 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>
@protocol OCTFileOutputProtocol <NSObject>
/**
* Prepare input to write. This method will be called before first call to writeData:.
*
* @return YES on success, NO on failure.
*/
- (BOOL)prepareToWrite;
/**
* Write data to output.
*
* @param data Data to write.
*
* @return YES on success, NO on failure.
*/
- (BOOL)writeData:(nonnull NSData *)data;
/**
* This method is called after last writeData: method.
*
* @return YES on success, NO on failure.
*/
- (BOOL)finishWriting;
/**
* This method is called if all progress was canceled. Do needed cleanup.
*/
- (void)cancel;
@end

View File

@ -0,0 +1,15 @@
// 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 "OCTFileInputProtocol.h"
@interface OCTFilePathInput : NSObject <OCTFileInputProtocol>
- (nullable instancetype)initWithFilePath:(nonnull NSString *)filePath;
- (nullable instancetype)init NS_UNAVAILABLE;
+ (nullable instancetype)new NS_UNAVAILABLE;
@end

View File

@ -0,0 +1,61 @@
// 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 "OCTFilePathInput.h"
#import "OCTLogging.h"
@interface OCTFilePathInput ()
@property (strong, nonatomic, readonly) NSString *filePath;
@property (strong, nonatomic) NSFileHandle *handle;
@end
@implementation OCTFilePathInput
#pragma mark - Lifecycle
- (nullable instancetype)initWithFilePath:(nonnull NSString *)filePath
{
self = [super init];
if (! self) {
return nil;
}
_filePath = filePath;
return self;
}
#pragma mark - OCTFileInputProtocol
- (BOOL)prepareToRead
{
self.handle = [NSFileHandle fileHandleForReadingAtPath:self.filePath];
if (! self.handle) {
return NO;
}
return YES;
}
- (NSData *)bytesWithPosition:(OCTToxFileSize)position length:(size_t)length
{
@try {
if (self.handle.offsetInFile != position) {
[self.handle seekToFileOffset:position];
}
return [self.handle readDataOfLength:length];
}
@catch (NSException *ex) {
OCTLogWarn(@"catched exception %@", ex);
}
return nil;
}
@end

View File

@ -0,0 +1,19 @@
// 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 "OCTFileOutputProtocol.h"
@interface OCTFilePathOutput : NSObject <OCTFileOutputProtocol>
@property (copy, nonatomic, readonly, nonnull) NSString *resultFilePath;
- (nullable instancetype)initWithTempFolder:(nonnull NSString *)tempFolder
resultFolder:(nonnull NSString *)resultFolder
fileName:(nonnull NSString *)fileName;
- (nullable instancetype)init NS_UNAVAILABLE;
+ (nullable instancetype)new NS_UNAVAILABLE;
@end

View File

@ -0,0 +1,100 @@
// 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 "OCTFilePathOutput.h"
#import "OCTLogging.h"
#import "OCTFileTools.h"
@interface OCTFilePathOutput ()
@property (copy, nonatomic, readonly, nonnull) NSString *tempFilePath;
@property (strong, nonatomic) NSFileHandle *handle;
@end
@implementation OCTFilePathOutput
#pragma mark - Lifecycle
- (nullable instancetype)initWithTempFolder:(nonnull NSString *)tempFolder
resultFolder:(nonnull NSString *)resultFolder
fileName:(nonnull NSString *)fileName
{
self = [super init];
if (! self) {
return nil;
}
_tempFilePath = [OCTFileTools createNewFilePathInDirectory:tempFolder fileName:fileName];
_resultFilePath = [OCTFileTools createNewFilePathInDirectory:resultFolder fileName:fileName];
// Create dummy file to reserve fileName.
[[NSFileManager defaultManager] createFileAtPath:_resultFilePath contents:[NSData data] attributes:nil];
OCTLogInfo(@"temp path %@", _tempFilePath);
OCTLogInfo(@"result path %@", _resultFilePath);
return self;
}
#pragma mark - OCTFileOutputProtocol
- (BOOL)prepareToWrite
{
if (! [[NSFileManager defaultManager] createFileAtPath:self.tempFilePath contents:nil attributes:nil]) {
return NO;
}
self.handle = [NSFileHandle fileHandleForWritingAtPath:self.tempFilePath];
if (! self.handle) {
return NO;
}
return YES;
}
- (BOOL)writeData:(nonnull NSData *)data
{
@try {
[self.handle writeData:data];
return YES;
}
@catch (NSException *ex) {
OCTLogWarn(@"catched exception %@", ex);
}
return NO;
}
- (BOOL)finishWriting
{
@try {
[self.handle synchronizeFile];
}
@catch (NSException *ex) {
OCTLogWarn(@"catched exception %@", ex);
return NO;
}
NSFileManager *fileManager = [NSFileManager defaultManager];
// Remove dummy file.
if (! [fileManager removeItemAtPath:self.resultFilePath error:nil]) {
return NO;
}
return [[NSFileManager defaultManager] moveItemAtPath:self.tempFilePath toPath:self.resultFilePath error:nil];
}
- (void)cancel
{
self.handle = nil;
[[NSFileManager defaultManager] removeItemAtPath:self.tempFilePath error:nil];
}
@end

View File

@ -0,0 +1,20 @@
// 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>
@interface OCTFileTools : NSObject
/**
* Creates filePath in directory for given fileName. In case if file already exists appends " N" suffix,
* e.g. "file 2.txt", "file 3.txt".
*
* @param directory Directory part of filePath.
* @param fileName Name of the file to use in path.
*
* @return File path to file that does not exist.
*/
+ (nonnull NSString *)createNewFilePathInDirectory:(nonnull NSString *)directory fileName:(nonnull NSString *)fileName;
@end

View File

@ -0,0 +1,77 @@
// 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 "OCTFileTools.h"
@implementation OCTFileTools
#pragma mark - Public
+ (nonnull NSString *)createNewFilePathInDirectory:(nonnull NSString *)directory fileName:(nonnull NSString *)fileName
{
NSParameterAssert(directory);
NSParameterAssert(fileName);
NSString *path = [directory stringByAppendingPathComponent:fileName];
if (! [self fileExistsAtPath:path]) {
return path;
}
NSString *base;
NSString *pathExtension = [fileName pathExtension];
NSInteger suffix;
[self getBaseString:&base andSuffix:&suffix fromString:[fileName stringByDeletingPathExtension]];
while (YES) {
NSString *resultName = [base stringByAppendingFormat:@" %ld", (long)suffix];
if (pathExtension.length > 0) {
resultName = [resultName stringByAppendingPathExtension:pathExtension];
}
NSString *path = [directory stringByAppendingPathComponent:resultName];
if (! [self fileExistsAtPath:path]) {
return path;
}
suffix++;
}
}
#pragma mark - Private
+ (BOOL)fileExistsAtPath:(NSString *)filePath
{
return [[NSFileManager defaultManager] fileExistsAtPath:filePath];
}
+ (void)getBaseString:(NSString **)baseString andSuffix:(NSInteger *)suffix fromString:(NSString *)original
{
NSString *tempBase = original;
NSInteger tempSuffix = 1;
NSArray *components = [tempBase componentsSeparatedByString:@" "];
if (components.count > 1) {
NSString *lastComponent = components.lastObject;
NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:lastComponent];
if ([[NSCharacterSet decimalDigitCharacterSet] isSupersetOfSet:set]) {
tempSuffix = [lastComponent integerValue];
// -1 for space.
NSInteger index = tempBase.length - lastComponent.length - 1;
tempBase = [tempBase substringToIndex:index];
}
}
tempSuffix++;
*baseString = tempBase;
*suffix = tempSuffix;
}
@end

View File

@ -0,0 +1,39 @@
// 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 "OCTFileBaseOperation.h"
@protocol OCTFileInputProtocol;
@interface OCTFileUploadOperation : OCTFileBaseOperation
@property (strong, nonatomic, readonly, nonnull) id<OCTFileInputProtocol> input;
/**
* Create operation.
*
* @param fileInput Input to use as a source for file transfer.
*
* For other parameters description see OCTFileBaseOperation.
*/
- (nullable instancetype)initWithTox:(nonnull OCTTox *)tox
fileInput:(nonnull id<OCTFileInputProtocol>)fileInput
friendNumber:(OCTToxFriendNumber)friendNumber
fileNumber:(OCTToxFileNumber)fileNumber
fileSize:(OCTToxFileSize)fileSize
userInfo:(nullable NSDictionary *)userInfo
progressBlock:(nullable OCTFileBaseOperationProgressBlock)progressBlock
etaUpdateBlock:(nullable OCTFileBaseOperationProgressBlock)etaUpdateBlock
successBlock:(nullable OCTFileBaseOperationSuccessBlock)successBlock
failureBlock:(nullable OCTFileBaseOperationFailureBlock)failureBlock;
/**
* Call this method to request next chunk.
*
* @param position The file or stream position from which to continue reading.
* @param length The number of bytes requested for the current chunk.
*/
- (void)chunkRequestWithPosition:(OCTToxFileSize)position length:(size_t)length;
@end

View File

@ -0,0 +1,109 @@
// 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 "OCTFileUploadOperation.h"
#import "OCTFileBaseOperation+Private.h"
#import "OCTFileInputProtocol.h"
#import "OCTLogging.h"
#import "NSError+OCTFile.h"
@interface OCTFileUploadOperation ()
@end
@implementation OCTFileUploadOperation
#pragma mark - Public
- (nullable instancetype)initWithTox:(nonnull OCTTox *)tox
fileInput:(nonnull id<OCTFileInputProtocol>)fileInput
friendNumber:(OCTToxFriendNumber)friendNumber
fileNumber:(OCTToxFileNumber)fileNumber
fileSize:(OCTToxFileSize)fileSize
userInfo:(nullable NSDictionary *)userInfo
progressBlock:(nullable OCTFileBaseOperationProgressBlock)progressBlock
etaUpdateBlock:(nullable OCTFileBaseOperationProgressBlock)etaUpdateBlock
successBlock:(nullable OCTFileBaseOperationSuccessBlock)successBlock
failureBlock:(nullable OCTFileBaseOperationFailureBlock)failureBlock
{
NSParameterAssert(fileInput);
self = [super initWithTox:tox
friendNumber:friendNumber
fileNumber:fileNumber
fileSize:fileSize
userInfo:userInfo
progressBlock:progressBlock
etaUpdateBlock:etaUpdateBlock
successBlock:successBlock
failureBlock:failureBlock];
if (! self) {
return nil;
}
_input = fileInput;
return self;
}
#pragma mark - Public
- (void)chunkRequestWithPosition:(OCTToxFileSize)position length:(size_t)length
{
if (length == 0) {
[self finishWithSuccess];
return;
}
NSData *data = [self.input bytesWithPosition:position length:length];
if (! data) {
[self finishWithError:[NSError sendFileErrorCannotReadFile]];
return;
}
NSError *error;
BOOL result;
do {
result = [self.tox fileSendChunkForFileNumber:self.fileNumber
friendNumber:self.friendNumber
position:position
data:data
error:&error];
if (! result) {
[NSThread sleepForTimeInterval:0.01];
}
}
while (! result && error.code == OCTToxErrorFileSendChunkSendq);
if (! result) {
OCTLogWarn(@"upload error %@", error);
[self.tox fileSendControlForFileNumber:self.fileNumber
friendNumber:self.friendNumber
control:OCTToxFileControlCancel
error:nil];
[self finishWithError:[NSError acceptFileErrorFromToxFileSendChunkError:error.code]];
return;
}
[self updateBytesDone:position + length];
}
#pragma mark - Override
- (void)operationStarted
{
[super operationStarted];
if (! [self.input prepareToRead]) {
[self finishWithError:[NSError sendFileErrorCannotReadFile]];
}
}
@end

View File

@ -0,0 +1,42 @@
// 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"
@class OCTTox;
NS_ASSUME_NONNULL_BEGIN
typedef void (^OCTSendMessageOperationSuccessBlock)(OCTToxMessageId messageId);
typedef void (^OCTSendMessageOperationFailureBlock)(NSError *error);
@interface OCTSendMessageOperation : NSOperation
/**
* Create operation.
*
* @param tox Tox object to send to.
* @param friendNumber Number of friend to send to.
* @param messageType Type of the message to send.
* @param message Message to send.
* @param msgv3HashHex the messageV3 Hash Hexstring or nil.
* @param successBlock Block called on operation success. Block will be called on main thread.
* @param failureBlock Block called on loading error. Block will be called on main thread.
*/
- (instancetype)initWithTox:(OCTTox *)tox
friendNumber:(OCTToxFriendNumber)friendNumber
messageType:(OCTToxMessageType)messageType
message:(NSString *)message
msgv3HashHex:(NSString *)msgv3HashHex
msgv3tssec:(UInt32)msgv3tssec
successBlock:(nullable OCTSendMessageOperationSuccessBlock)successBlock
failureBlock:(nullable OCTSendMessageOperationFailureBlock)failureBlock;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,80 @@
// 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 "OCTSendMessageOperation.h"
#import "OCTTox.h"
@interface OCTSendMessageOperation ()
@property (weak, nonatomic, readonly) OCTTox *tox;
@property (assign, nonatomic, readonly) OCTToxFriendNumber friendNumber;
@property (assign, nonatomic, readonly) OCTToxMessageType messageType;
@property (copy, nonatomic, readonly) NSString *message;
@property (copy, nonatomic, readonly) NSString *msgv3HashHex;
@property (assign, nonatomic, readonly) UInt32 msgv3tssec;
@property (copy, nonatomic, readonly) OCTSendMessageOperationSuccessBlock successBlock;
@property (copy, nonatomic, readonly) OCTSendMessageOperationFailureBlock failureBlock;
@end
@implementation OCTSendMessageOperation
- (instancetype)initWithTox:(OCTTox *)tox
friendNumber:(OCTToxFriendNumber)friendNumber
messageType:(OCTToxMessageType)messageType
message:(NSString *)message
msgv3HashHex:(NSString *)msgv3HashHex
msgv3tssec:(UInt32)msgv3tssec
successBlock:(nullable OCTSendMessageOperationSuccessBlock)successBlock
failureBlock:(nullable OCTSendMessageOperationFailureBlock)failureBlock
{
self = [super init];
if (! self) {
return nil;
}
_tox = tox;
_friendNumber = friendNumber;
_messageType = messageType;
_message = [message copy];
_msgv3HashHex = [msgv3HashHex copy];
_msgv3tssec = msgv3tssec;
_successBlock = [successBlock copy];
_failureBlock = [failureBlock copy];
return self;
}
- (void)main
{
if (self.cancelled) {
return;
}
NSError *error;
OCTToxMessageId messageId = [self.tox sendMessageWithFriendNumber:self.friendNumber
type:self.messageType
message:self.message
msgv3HashHex:self.msgv3HashHex
msgv3tssec:self.msgv3tssec
error:&error];
dispatch_async(dispatch_get_main_queue(), ^{
if (self.cancelled) {
return;
}
if (error && self.failureBlock) {
self.failureBlock(error);
}
else if (! error && self.successBlock) {
self.successBlock(messageId);
}
});
}
@end

View File

@ -0,0 +1,8 @@
// 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 "OCTManagerConstants.h"
NSString *const kOCTManagerErrorDomain = @"me.dvor.objcTox.OCTManagerErrorDomain";

View File

@ -0,0 +1,475 @@
// 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 "OCTManagerFactory.h"
#import "OCTManagerImpl.h"
#import "OCTManagerConfiguration.h"
#import "OCTRealmManager.h"
#import "OCTTox.h"
#import "OCTToxEncryptSave.h"
#import "OCTToxEncryptSaveConstants.h"
typedef NS_ENUM(NSInteger, OCTDecryptionErrorFileType) {
OCTDecryptionErrorFileTypeDatabaseKey,
OCTDecryptionErrorFileTypeToxFile,
};
static const NSUInteger kEncryptedKeyLength = 64;
@implementation OCTManagerFactory
+ (void)managerWithConfiguration:(OCTManagerConfiguration *)configuration
encryptPassword:(nonnull NSString *)encryptPassword
successBlock:(void (^)(id<OCTManager> manager))successBlock
failureBlock:(void (^)(NSError *error))failureBlock
{
[self validateConfiguration:configuration];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
// Decrypting Realm.
__block NSData *realmEncryptionKey = nil;
__block NSError *decryptRealmError = nil;
dispatch_group_async(group, queue, ^{
realmEncryptionKey = [self realmEncryptionKeyWithConfiguration:configuration
password:encryptPassword
error:&decryptRealmError];
});
// Decrypting Tox save.
__block NSError *decryptToxError = nil;
__block OCTToxEncryptSave *encryptSave = nil;
__block NSData *toxSave = nil;
dispatch_group_async(group, queue, ^{
if (! [self importToxSaveIfNeeded:configuration error:&decryptToxError]) {
return;
}
NSData *savedData = [self getSavedDataFromPath:configuration.fileStorage.pathForToxSaveFile];
encryptSave = [self toxEncryptSaveWithToxPassword:encryptPassword savedData:savedData error:&decryptToxError];
if (! encryptSave) {
return;
}
savedData = [self decryptSavedData:savedData encryptSave:encryptSave error:&decryptToxError];
if (! savedData) {
return;
}
toxSave = savedData;
});
dispatch_async(queue, ^{
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
NSError *error = decryptRealmError ?: decryptToxError;
if (error) {
if (failureBlock) {
failureBlock(error);
}
return;
}
OCTTox *tox = [self createToxWithOptions:configuration.options toxData:toxSave error:&error];
if (! tox) {
if (failureBlock) {
failureBlock(error);
}
return;
}
NSURL *databaseFileURL = [NSURL fileURLWithPath:configuration.fileStorage.pathForDatabase];
OCTRealmManager *realmManager = [[OCTRealmManager alloc] initWithDatabaseFileURL:databaseFileURL encryptionKey:realmEncryptionKey];
OCTManagerImpl *manager = [[OCTManagerImpl alloc] initWithConfiguration:configuration
tox:tox
toxEncryptSave:encryptSave
realmManager:realmManager];
if (successBlock) {
successBlock(manager);
}
});
});
}
#pragma mark - Private
+ (void)validateConfiguration:(OCTManagerConfiguration *)configuration
{
NSParameterAssert(configuration.fileStorage);
NSParameterAssert(configuration.fileStorage.pathForDownloadedFilesDirectory);
NSParameterAssert(configuration.fileStorage.pathForUploadedFilesDirectory);
NSParameterAssert(configuration.fileStorage.pathForTemporaryFilesDirectory);
NSParameterAssert(configuration.options);
}
+ (NSData *)realmEncryptionKeyWithConfiguration:(OCTManagerConfiguration *)configuration
password:(NSString *)databasePassword
error:(NSError **)error
{
NSString *databasePath = configuration.fileStorage.pathForDatabase;
NSString *encryptedKeyPath = configuration.fileStorage.pathForDatabaseEncryptionKey;
BOOL databaseExists = [[NSFileManager defaultManager] fileExistsAtPath:databasePath];
BOOL encryptedKeyExists = [[NSFileManager defaultManager] fileExistsAtPath:encryptedKeyPath];
if (! databaseExists && ! encryptedKeyExists) {
// First run, create key and database.
if (! [self createEncryptedKeyAtPath:encryptedKeyPath withPassword:databasePassword]) {
[self fillError:error withInitErrorCode:OCTManagerInitErrorDatabaseKeyCannotCreateKey];
return nil;
}
}
if (databaseExists && ! encryptedKeyExists) {
// It seems that we found old unencrypted database, let's migrate to encrypted one.
NSError *migrationError;
BOOL result = [self migrateToEncryptedDatabase:databasePath
encryptionKeyPath:encryptedKeyPath
withPassword:databasePassword
error:&migrationError];
if (! result) {
if (error) {
*error = [NSError errorWithDomain:kOCTManagerErrorDomain code:OCTManagerInitErrorDatabaseKeyMigrationToEncryptedFailed userInfo:@{
NSLocalizedDescriptionKey : migrationError.localizedDescription,
NSLocalizedFailureReasonErrorKey : migrationError.localizedFailureReason,
}];
}
return nil;
}
}
NSData *encryptedKey = [NSData dataWithContentsOfFile:encryptedKeyPath];
if (! encryptedKey) {
[self fillError:error withInitErrorCode:OCTManagerInitErrorDatabaseKeyCannotReadKey];
return nil;
}
NSError *decryptError;
NSData *key = [OCTToxEncryptSave decryptData:encryptedKey withPassphrase:databasePassword error:&decryptError];
if (! key) {
[self fillError:error withDecryptionError:decryptError.code fileType:OCTDecryptionErrorFileTypeDatabaseKey];
return nil;
}
return key;
}
+ (BOOL)createEncryptedKeyAtPath:(NSString *)path withPassword:(NSString *)password
{
NSMutableData *key = [NSMutableData dataWithLength:kEncryptedKeyLength];
SecRandomCopyBytes(kSecRandomDefault, key.length, (uint8_t *)key.mutableBytes);
NSData *encryptedKey = [OCTToxEncryptSave encryptData:key withPassphrase:password error:nil];
return [encryptedKey writeToFile:path options:NSDataWritingAtomic error:nil];
}
+ (BOOL)fillError:(NSError **)error withDecryptionError:(OCTToxEncryptSaveDecryptionError)code fileType:(OCTDecryptionErrorFileType)fileType
{
if (! error) {
return NO;
}
NSDictionary *mapping;
switch (fileType) {
case OCTDecryptionErrorFileTypeDatabaseKey:
mapping = @{
@(OCTToxEncryptSaveDecryptionErrorNull) : @(OCTManagerInitErrorDatabaseKeyDecryptNull),
@(OCTToxEncryptSaveDecryptionErrorBadFormat) : @(OCTManagerInitErrorDatabaseKeyDecryptBadFormat),
@(OCTToxEncryptSaveDecryptionErrorFailed) : @(OCTManagerInitErrorDatabaseKeyDecryptFailed),
};
break;
case OCTDecryptionErrorFileTypeToxFile:
mapping = @{
@(OCTToxEncryptSaveDecryptionErrorNull) : @(OCTManagerInitErrorToxFileDecryptNull),
@(OCTToxEncryptSaveDecryptionErrorBadFormat) : @(OCTManagerInitErrorToxFileDecryptBadFormat),
@(OCTToxEncryptSaveDecryptionErrorFailed) : @(OCTManagerInitErrorToxFileDecryptFailed),
};
break;
}
OCTManagerInitError initErrorCode = [mapping[@(code)] integerValue];
[self fillError:error withInitErrorCode:initErrorCode];
return YES;
}
+ (BOOL)fillError:(NSError **)error withInitErrorCode:(OCTManagerInitError)code
{
if (! error) {
return NO;
}
NSString *failureReason = nil;
switch (code) {
case OCTManagerInitErrorPassphraseFailed:
failureReason = @"Cannot create symmetric key from given passphrase.";
break;
case OCTManagerInitErrorCannotImportToxSave:
failureReason = @"Cannot copy tox save at `importToxSaveFromPath` path.";
break;
case OCTManagerInitErrorDatabaseKeyCannotCreateKey:
failureReason = @"Cannot create encryption key.";
break;
case OCTManagerInitErrorDatabaseKeyCannotReadKey:
failureReason = @"Cannot read encryption key.";
break;
case OCTManagerInitErrorDatabaseKeyMigrationToEncryptedFailed:
// Nothing to do here, this error will be created elsewhere.
break;
case OCTManagerInitErrorDatabaseKeyDecryptNull:
failureReason = @"Cannot decrypt database key file. Some input data was empty.";
break;
case OCTManagerInitErrorDatabaseKeyDecryptBadFormat:
failureReason = @"Cannot decrypt database key file. Data has bad format.";
break;
case OCTManagerInitErrorDatabaseKeyDecryptFailed:
failureReason = @"Cannot decrypt database key file. The encrypted byte array could not be decrypted. Either the data was corrupt or the password/key was incorrect.";
break;
case OCTManagerInitErrorToxFileDecryptNull:
failureReason = @"Cannot decrypt tox save file. Some input data was empty.";
break;
case OCTManagerInitErrorToxFileDecryptBadFormat:
failureReason = @"Cannot decrypt tox save file. Data has bad format.";
break;
case OCTManagerInitErrorToxFileDecryptFailed:
failureReason = @"Cannot decrypt tox save file. The encrypted byte array could not be decrypted. Either the data was corrupt or the password/key was incorrect.";
break;
case OCTManagerInitErrorCreateToxUnknown:
failureReason = @"Cannot create tox. Unknown error occurred.";
break;
case OCTManagerInitErrorCreateToxMemoryError:
failureReason = @"Cannot create tox. Was unable to allocate enough memory to store the internal structures for the Tox object.";
break;
case OCTManagerInitErrorCreateToxPortAlloc:
failureReason = @"Cannot create tox. Was unable to bind to a port.";
break;
case OCTManagerInitErrorCreateToxProxyBadType:
failureReason = @"Cannot create tox. Proxy type was invalid.";
break;
case OCTManagerInitErrorCreateToxProxyBadHost:
failureReason = @"Cannot create tox. proxyAddress had an invalid format or was nil (while proxyType was set).";
break;
case OCTManagerInitErrorCreateToxProxyBadPort:
failureReason = @"Cannot create tox. Proxy port was invalid.";
break;
case OCTManagerInitErrorCreateToxProxyNotFound:
failureReason = @"Cannot create tox. The proxy host passed could not be resolved.";
break;
case OCTManagerInitErrorCreateToxEncrypted:
failureReason = @"Cannot create tox. The saved data to be loaded contained an encrypted save.";
break;
case OCTManagerInitErrorCreateToxBadFormat:
failureReason = @"Cannot create tox. Data has bad format.";
break;
}
*error = [NSError errorWithDomain:kOCTManagerErrorDomain code:code userInfo:@{
NSLocalizedDescriptionKey : @"Cannot create OCTManager",
NSLocalizedFailureReasonErrorKey : failureReason
}];
return YES;
}
+ (BOOL)migrateToEncryptedDatabase:(NSString *)databasePath
encryptionKeyPath:(NSString *)encryptionKeyPath
withPassword:(NSString *)password
error:(NSError **)error
{
NSParameterAssert(databasePath);
NSParameterAssert(encryptionKeyPath);
NSParameterAssert(password);
if ([[NSFileManager defaultManager] fileExistsAtPath:encryptionKeyPath]) {
if (error) {
*error = [NSError errorWithDomain:kOCTManagerErrorDomain code:100 userInfo:@{
NSLocalizedDescriptionKey : @"Cannot migrate unencrypted database to encrypted",
NSLocalizedFailureReasonErrorKey : @"Database is already encrypted",
}];
}
return NO;
}
NSString *tempKeyPath = [encryptionKeyPath stringByAppendingPathExtension:@"tmp"];
if (! [self createEncryptedKeyAtPath:tempKeyPath withPassword:password]) {
if (error) {
*error = [NSError errorWithDomain:kOCTManagerErrorDomain code:101 userInfo:@{
NSLocalizedDescriptionKey : @"Cannot migrate unencrypted database to encrypted",
NSLocalizedFailureReasonErrorKey : @"Cannot create encryption key",
}];
}
return NO;
}
NSData *encryptedKey = [NSData dataWithContentsOfFile:tempKeyPath];
if (! encryptedKey) {
if (error) {
*error = [NSError errorWithDomain:kOCTManagerErrorDomain code:102 userInfo:@{
NSLocalizedDescriptionKey : @"Cannot migrate unencrypted database to encrypted",
NSLocalizedFailureReasonErrorKey : @"Cannot find encryption key",
}];
}
return NO;
}
NSData *key = [OCTToxEncryptSave decryptData:encryptedKey withPassphrase:password error:error];
if (! key) {
return NO;
}
if (! [OCTRealmManager migrateToEncryptedDatabase:databasePath encryptionKey:key error:error]) {
return NO;
}
if (! [[NSFileManager defaultManager] moveItemAtPath:tempKeyPath toPath:encryptionKeyPath error:error]) {
return NO;
}
return YES;
}
+ (BOOL)importToxSaveIfNeeded:(OCTManagerConfiguration *)configuration error:(NSError **)error
{
BOOL result = YES;
NSFileManager *fileManager = [NSFileManager defaultManager];
if (configuration.importToxSaveFromPath && [fileManager fileExistsAtPath:configuration.importToxSaveFromPath]) {
result = [fileManager copyItemAtPath:configuration.importToxSaveFromPath
toPath:configuration.fileStorage.pathForToxSaveFile
error:nil];
}
if (! result) {
[self fillError:error withInitErrorCode:OCTManagerInitErrorCannotImportToxSave];
}
return result;
}
+ (OCTToxEncryptSave *)toxEncryptSaveWithToxPassword:(NSString *)toxPassword
savedData:(NSData *)savedData
error:(NSError **)error
{
OCTToxEncryptSave *encryptSave;
if (savedData && [OCTToxEncryptSave isDataEncrypted:savedData]) {
encryptSave = [[OCTToxEncryptSave alloc] initWithPassphrase:toxPassword toxData:savedData error:nil];
}
else {
// Save data wasn't encrypted. Passing nil as toxData parameter to encrypt it next time.
encryptSave = [[OCTToxEncryptSave alloc] initWithPassphrase:toxPassword toxData:nil error:nil];
}
if (! encryptSave) {
[self fillError:error withInitErrorCode:OCTManagerInitErrorPassphraseFailed];
return nil;
}
return encryptSave;
}
+ (NSData *)decryptSavedData:(NSData *)data encryptSave:(OCTToxEncryptSave *)encryptSave error:(NSError **)error
{
NSParameterAssert(encryptSave);
if (! data) {
return data;
}
if (! [OCTToxEncryptSave isDataEncrypted:data]) {
// Tox data wasn't encrypted, nothing to do here.
return data;
}
NSError *decryptError = nil;
NSData *result = [encryptSave decryptData:data error:&decryptError];
if (result) {
return result;
}
[self fillError:error withDecryptionError:decryptError.code fileType:OCTDecryptionErrorFileTypeToxFile];
return nil;
}
+ (NSData *)getSavedDataFromPath:(NSString *)path
{
return [[NSFileManager defaultManager] fileExistsAtPath:path] ?
([NSData dataWithContentsOfFile:path]) :
nil;
}
+ (OCTTox *)createToxWithOptions:(OCTToxOptions *)options toxData:(NSData *)toxData error:(NSError **)error
{
NSError *toxError = nil;
OCTTox *tox = [[OCTTox alloc] initWithOptions:options savedData:toxData error:&toxError];
if (tox) {
return tox;
}
OCTToxErrorInitCode code = toxError.code;
switch (code) {
case OCTToxErrorInitCodeUnknown:
[self fillError:error withInitErrorCode:OCTManagerInitErrorCreateToxUnknown];
break;
case OCTToxErrorInitCodeMemoryError:
[self fillError:error withInitErrorCode:OCTManagerInitErrorCreateToxMemoryError];
break;
case OCTToxErrorInitCodePortAlloc:
[self fillError:error withInitErrorCode:OCTManagerInitErrorCreateToxPortAlloc];
break;
case OCTToxErrorInitCodeProxyBadType:
[self fillError:error withInitErrorCode:OCTManagerInitErrorCreateToxProxyBadType];
break;
case OCTToxErrorInitCodeProxyBadHost:
[self fillError:error withInitErrorCode:OCTManagerInitErrorCreateToxProxyBadHost];
break;
case OCTToxErrorInitCodeProxyBadPort:
[self fillError:error withInitErrorCode:OCTManagerInitErrorCreateToxProxyBadPort];
break;
case OCTToxErrorInitCodeProxyNotFound:
[self fillError:error withInitErrorCode:OCTManagerInitErrorCreateToxProxyNotFound];
break;
case OCTToxErrorInitCodeEncrypted:
[self fillError:error withInitErrorCode:OCTManagerInitErrorCreateToxEncrypted];
break;
case OCTToxErrorInitCodeLoadBadFormat:
[self fillError:error withInitErrorCode:OCTManagerInitErrorCreateToxBadFormat];
break;
}
return nil;
}
@end

View File

@ -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 <Foundation/Foundation.h>
#import "OCTManager.h"
NS_ASSUME_NONNULL_BEGIN
@class OCTManagerConfiguration;
@class OCTTox;
@class OCTToxEncryptSave;
@class OCTRealmManager;
@interface OCTManagerImpl : NSObject <OCTManager>
- (instancetype)initWithConfiguration:(OCTManagerConfiguration *)configuration
tox:(OCTTox *)tox
toxEncryptSave:(OCTToxEncryptSave *)toxEncryptSave
realmManager:(OCTRealmManager *)realmManager;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,347 @@
// 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 <objc/runtime.h>
#import "OCTManagerImpl.h"
#import "OCTTox.h"
#import "OCTToxEncryptSave.h"
#import "OCTManagerConfiguration.h"
#import "OCTManagerFactory.h"
#import "OCTSubmanagerBootstrapImpl.h"
#import "OCTSubmanagerCallsImpl.h"
#import "OCTSubmanagerChatsImpl.h"
#import "OCTSubmanagerFilesImpl.h"
#import "OCTSubmanagerFriendsImpl.h"
#import "OCTSubmanagerObjectsImpl.h"
#import "OCTSubmanagerUserImpl.h"
#import "OCTRealmManager.h"
@interface OCTManagerImpl () <OCTToxDelegate, OCTSubmanagerDataSource>
@property (copy, nonatomic, readonly) OCTManagerConfiguration *currentConfiguration;
@property (strong, nonatomic, readonly) OCTTox *tox;
@property (strong, nonatomic, readonly) NSObject *toxSaveFileLock;
@property (strong, nonatomic, nonnull) OCTToxEncryptSave *encryptSave;
@property (strong, nonatomic, readonly) OCTRealmManager *realmManager;
@property (strong, atomic) NSNotificationCenter *notificationCenter;
@property (strong, nonatomic, readwrite) OCTSubmanagerBootstrapImpl *bootstrap;
@property (strong, nonatomic, readwrite) OCTSubmanagerCallsImpl *calls;
@property (strong, nonatomic, readwrite) OCTSubmanagerChatsImpl *chats;
@property (strong, nonatomic, readwrite) OCTSubmanagerFilesImpl *files;
@property (strong, nonatomic, readwrite) OCTSubmanagerFriendsImpl *friends;
@property (strong, nonatomic, readwrite) OCTSubmanagerObjectsImpl *objects;
@property (strong, nonatomic, readwrite) OCTSubmanagerUserImpl *user;
@end
@implementation OCTManagerImpl
#pragma mark - Lifecycle
- (instancetype)initWithConfiguration:(OCTManagerConfiguration *)configuration
tox:(OCTTox *)tox
toxEncryptSave:(OCTToxEncryptSave *)toxEncryptSave
realmManager:(OCTRealmManager *)realmManager
{
self = [super init];
if (! self) {
return nil;
}
_currentConfiguration = [configuration copy];
_tox = tox;
_tox.delegate = self;
_toxSaveFileLock = [NSObject new];
_encryptSave = toxEncryptSave;
_realmManager = realmManager;
_notificationCenter = [[NSNotificationCenter alloc] init];
[_tox start];
[self saveTox];
[self createSubmanagers];
return self;
}
- (void)dealloc
{
[self killSubmanagers];
[self.tox stop];
}
#pragma mark - Public
- (OCTManagerConfiguration *)configuration
{
return [self.currentConfiguration copy];
}
- (NSString *)exportToxSaveFileAndReturnError:(NSError **)error
{
@synchronized(self.toxSaveFileLock) {
NSString *savedDataPath = self.currentConfiguration.fileStorage.pathForToxSaveFile;
NSString *tempPath = self.currentConfiguration.fileStorage.pathForTemporaryFilesDirectory;
tempPath = [tempPath stringByAppendingPathComponent:[savedDataPath lastPathComponent]];
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:tempPath]) {
[fileManager removeItemAtPath:tempPath error:error];
}
if (! [fileManager copyItemAtPath:savedDataPath toPath:tempPath error:error]) {
return nil;
}
return tempPath;
}
}
- (BOOL)changeEncryptPassword:(nonnull NSString *)newPassword oldPassword:(nonnull NSString *)oldPassword
{
OCTToxEncryptSave *encryptSave = [self changeToxPassword:newPassword oldPassword:oldPassword];
if (encryptSave == nil) {
return NO;
}
if (! [self changeDatabasePassword:newPassword oldPassword:oldPassword]) {
return NO;
}
self.encryptSave = encryptSave;
[self saveTox];
return YES;
}
- (BOOL)isManagerEncryptedWithPassword:(nonnull NSString *)password
{
NSString *toxFilePath = self.currentConfiguration.fileStorage.pathForToxSaveFile;
return [self isDataAtPath:toxFilePath encryptedWithPassword:password];
}
#pragma mark - OCTSubmanagerDataSource
- (OCTTox *)managerGetTox
{
return self.tox;
}
- (BOOL)managerIsToxConnected
{
return (self.user.connectionStatus != OCTToxConnectionStatusNone);
}
- (void)managerSaveTox
{
return [self saveTox];
}
- (OCTRealmManager *)managerGetRealmManager
{
return self.realmManager;
}
- (id<OCTFileStorageProtocol>)managerGetFileStorage
{
return self.currentConfiguration.fileStorage;
}
- (NSNotificationCenter *)managerGetNotificationCenter
{
return self.notificationCenter;
}
- (BOOL)managerUseFauxOfflineMessaging
{
return self.currentConfiguration.useFauxOfflineMessaging;
}
#pragma mark - Private
- (NSData *)getSavedDataFromPath:(NSString *)path
{
return [[NSFileManager defaultManager] fileExistsAtPath:path] ?
([NSData dataWithContentsOfFile:path]) :
nil;
}
- (BOOL)isDataAtPath:(NSString *)path encryptedWithPassword:(NSString *)password
{
NSData *savedData = [self getSavedDataFromPath:path];
if (! savedData) {
return NO;
}
if ([OCTToxEncryptSave isDataEncrypted:savedData]) {
return [OCTToxEncryptSave decryptData:savedData withPassphrase:password error:nil] != nil;
}
return NO;
}
- (void)createSubmanagers
{
_bootstrap = [self createSubmanagerWithClass:[OCTSubmanagerBootstrapImpl class]];
_chats = [self createSubmanagerWithClass:[OCTSubmanagerChatsImpl class]];
_files = [self createSubmanagerWithClass:[OCTSubmanagerFilesImpl class]];
_friends = [self createSubmanagerWithClass:[OCTSubmanagerFriendsImpl class]];
_objects = [self createSubmanagerWithClass:[OCTSubmanagerObjectsImpl class]];
_user = [self createSubmanagerWithClass:[OCTSubmanagerUserImpl class]];
OCTSubmanagerCallsImpl *calls = [[OCTSubmanagerCallsImpl alloc] initWithTox:_tox];
calls.dataSource = self;
_calls = calls;
[_calls setupAndReturnError:nil];
}
- (void)killSubmanagers
{
self.bootstrap = nil;
self.calls = nil;
self.chats = nil;
self.files = nil;
self.friends = nil;
self.objects = nil;
self.user = nil;
}
- (id)createSubmanagerWithClass:(Class)class
{
id<OCTSubmanagerProtocol> submanager = [[class alloc] init];
submanager.dataSource = self;
if ([submanager respondsToSelector:@selector(configure)]) {
[submanager configure];
}
return submanager;
}
- (BOOL)respondsToSelector:(SEL)aSelector
{
id submanager = [self forwardingTargetForSelector:aSelector];
if (submanager) {
return YES;
}
return [super respondsToSelector:aSelector];
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
struct objc_method_description description = protocol_getMethodDescription(@protocol(OCTToxDelegate), aSelector, NO, YES);
if (description.name == NULL) {
// We forward methods only from OCTToxDelegate protocol.
return nil;
}
NSArray *submanagers = @[
self.bootstrap,
self.chats,
self.files,
self.friends,
self.objects,
self.user,
];
for (id delegate in submanagers) {
if ([delegate respondsToSelector:aSelector]) {
return delegate;
}
}
return nil;
}
- (void)saveTox
{
@synchronized(self.toxSaveFileLock) {
void (^throwException)(NSError *) = ^(NSError *error) {
NSDictionary *userInfo = nil;
if (error) {
userInfo = @{ @"NSError" : error };
}
@throw [NSException exceptionWithName:@"saveToxException" reason:error.debugDescription userInfo:userInfo];
};
NSData *data = [self.tox save];
NSError *error;
data = [self.encryptSave encryptData:data error:&error];
if (! data) {
throwException(error);
}
if (! [data writeToFile:self.currentConfiguration.fileStorage.pathForToxSaveFile options:NSDataWritingAtomic error:&error]) {
throwException(error);
}
}
}
// On success returns encryptSave with new password.
- (OCTToxEncryptSave *)changeToxPassword:(NSString *)newPassword oldPassword:(NSString *)oldPassword
{
NSString *toxFilePath = self.currentConfiguration.fileStorage.pathForToxSaveFile;
if (! [self isDataAtPath:toxFilePath encryptedWithPassword:oldPassword]) {
return nil;
}
__block OCTToxEncryptSave *newEncryptSave;
@synchronized(self.toxSaveFileLock) {
// Passing nil as tox data as we are setting new password.
newEncryptSave = [[OCTToxEncryptSave alloc] initWithPassphrase:newPassword toxData:nil error:nil];
}
return newEncryptSave;
}
- (BOOL)changeDatabasePassword:(NSString *)newPassword oldPassword:(NSString *)oldPassword
{
NSParameterAssert(newPassword);
NSParameterAssert(oldPassword);
NSString *encryptedKeyPath = self.currentConfiguration.fileStorage.pathForDatabaseEncryptionKey;
NSData *encryptedKey = [NSData dataWithContentsOfFile:encryptedKeyPath];
if (! encryptedKey) {
return NO;
}
NSData *key = [OCTToxEncryptSave decryptData:encryptedKey withPassphrase:oldPassword error:nil];
if (! key) {
return NO;
}
NSData *newEncryptedKey = [OCTToxEncryptSave encryptData:key withPassphrase:newPassword error:nil];
if (! newEncryptedKey) {
return NO;
}
return [newEncryptedKey writeToFile:encryptedKeyPath options:NSDataWritingAtomic error:nil];
}
@end

View File

@ -0,0 +1,11 @@
// 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 "OCTCall.h"
@interface OCTCall (Utilities)
- (BOOL)isPaused;
@end

View File

@ -0,0 +1,14 @@
// 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 "OCTCall+Utilities.h"
@implementation OCTCall (Utilities)
- (BOOL)isPaused
{
return (self.pausedStatus != OCTCallPausedStatusNone);
}
@end

View File

@ -0,0 +1,28 @@
// 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 "OCTCall+Utilities.h"
#import "OCTToxAVConstants.h"
@interface OCTCall ()
@end
@implementation OCTCall
- (BOOL)isOutgoing
{
return (self.caller == nil);
}
- (NSDate *)onHoldDate
{
if (self.onHoldStartInterval <= 0) {
return nil;
}
return [NSDate dateWithTimeIntervalSince1970:self.onHoldStartInterval];
}
@end

View File

@ -0,0 +1,25 @@
// 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 OCTRealmManager;
@class OCTCall;
@interface OCTCallTimer : NSObject
- (instancetype)initWithRealmManager:(OCTRealmManager *)realmManager;
/**
* Starts the timer for the specified call.
* Note that there can only be one active call.
* @param call Call to update.
*/
- (void)startTimerForCall:(OCTCall *)call;
/**
* Stops the timer for the current call in session.
*/
- (void)stopTimer;
@end

View File

@ -0,0 +1,87 @@
// 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 "OCTCallTimer.h"
#import "OCTRealmManager.h"
#import "OCTCall.h"
#import "OCTLogging.h"
@interface OCTCallTimer ()
@property (strong, nonatomic) dispatch_source_t timer;
@property (strong, nonatomic) OCTRealmManager *realmManager;
@property (strong, nonatomic) OCTCall *call;
@end
@implementation OCTCallTimer
- (instancetype)initWithRealmManager:(OCTRealmManager *)realmManager
{
self = [super init];
if (! self) {
return nil;
}
_realmManager = realmManager;
return self;
}
- (void)startTimerForCall:(OCTCall *)call
{
@synchronized(self) {
if (self.timer) {
NSAssert(! self.timer, @"There is already a timer in progress!");
}
self.call = call;
// dispatch_queue_t queue = dispatch_queue_create("me.dvor.objcTox.OCTCallQueue", DISPATCH_QUEUE_SERIAL);
// Main queue is used temporarily for now since we are getting 'Realm accessed from incorrect thread'.
// Should really be using the queue above..
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
uint64_t interval = NSEC_PER_SEC;
uint64_t leeway = NSEC_PER_SEC / 1000;
dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, interval, leeway);
__weak OCTCallTimer *weakSelf = self;
dispatch_source_set_event_handler(self.timer, ^{
OCTCallTimer *strongSelf = weakSelf;
if (! strongSelf) {
dispatch_source_cancel(self.timer);
OCTLogError(@"Error: Attempt to update timer with no strong pointer to OCTCallTimer");
return;
}
[strongSelf.realmManager updateObject:strongSelf.call withBlock:^(OCTCall *callToUpdate) {
callToUpdate.callDuration += 1.0;
}];
OCTLogInfo(@"Call: %@ duration at %f seconds", strongSelf.call, strongSelf.call.callDuration);
});
dispatch_resume(self.timer);
}
}
- (void)stopTimer
{
@synchronized(self) {
if (! self.timer) {
return;
}
OCTLogInfo(@"Timer for call %@ has stopped at duration %f", self.call, self.call.callDuration);
dispatch_source_cancel(self.timer);
self.timer = nil;
self.call = nil;
}
}
@end

View File

@ -0,0 +1,39 @@
// 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 "OCTChat.h"
#import "OCTMessageAbstract.h"
@interface OCTChat ()
@end
@implementation OCTChat
#pragma mark - Public
- (NSDate *)lastReadDate
{
if (self.lastReadDateInterval <= 0) {
return nil;
}
return [NSDate dateWithTimeIntervalSince1970:self.lastReadDateInterval];
}
- (NSDate *)lastActivityDate
{
if (self.lastActivityDateInterval <= 0) {
return nil;
}
return [NSDate dateWithTimeIntervalSince1970:self.lastActivityDateInterval];
}
- (BOOL)hasUnreadMessages
{
return (self.lastMessage.dateInterval > self.lastReadDateInterval);
}
@end

View File

@ -0,0 +1,41 @@
// 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 "OCTFriend.h"
@interface OCTFriend ()
@end
@implementation OCTFriend
#pragma mark - Class methods
+ (NSArray *)requiredProperties
{
NSMutableArray *properties = [NSMutableArray arrayWithArray:[super requiredProperties]];
[properties addObject:NSStringFromSelector(@selector(nickname))];
[properties addObject:NSStringFromSelector(@selector(publicKey))];
return [properties copy];
}
#pragma mark - Public
- (NSDate *)lastSeenOnline
{
if (self.lastSeenOnlineInterval <= 0) {
return nil;
}
return [NSDate dateWithTimeIntervalSince1970:self.lastSeenOnlineInterval];
}
- (NSString *)description
{
return [NSString stringWithFormat:@"OCTFriend with friendNumber %u, name %@", self.friendNumber, self.name];
}
@end

View File

@ -0,0 +1,33 @@
// 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 "OCTFriendRequest.h"
@implementation OCTFriendRequest
#pragma mark - Class methods
+ (NSArray *)requiredProperties
{
NSMutableArray *properties = [NSMutableArray arrayWithArray:[super requiredProperties]];
[properties addObject:NSStringFromSelector(@selector(publicKey))];
return [properties copy];
}
#pragma mark - Public
- (NSDate *)date
{
return [NSDate dateWithTimeIntervalSince1970:self.dateInterval];
}
- (NSString *)description
{
return [NSString stringWithFormat:@"OCTFriendRequest with publicKey %@...\nmessage length %lu",
[self.publicKey substringToIndex:5], (unsigned long)self.message.length];
}
@end

View File

@ -0,0 +1,49 @@
// 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 "OCTMessageAbstract.h"
#import "OCTMessageText.h"
#import "OCTMessageFile.h"
#import "OCTMessageCall.h"
@interface OCTMessageAbstract ()
@end
@implementation OCTMessageAbstract
#pragma mark - Public
- (NSDate *)date
{
if (self.dateInterval <= 0) {
return nil;
}
return [NSDate dateWithTimeIntervalSince1970:self.dateInterval];
}
- (BOOL)isOutgoing
{
return (self.senderUniqueIdentifier == nil);
}
- (NSString *)description
{
NSString *string = nil;
if (self.messageText) {
string = [self.messageText description];
}
else if (self.messageFile) {
string = [self.messageFile description];
}
else if (self.messageCall) {
string = [self.messageCall description];
}
return [NSString stringWithFormat:@"OCTMessageAbstract with date %@, %@", self.date, string];
}
@end

View File

@ -0,0 +1,34 @@
// 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 "OCTMessageCall.h"
@implementation OCTMessageCall
#pragma mark - Public
- (NSString *)description
{
NSString *description = [super description];
return [description stringByAppendingString:[self typeDescription]];
}
#pragma mark - Private
- (NSString *)typeDescription
{
NSString *description;
switch (self.callEvent) {
case OCTMessageCallEventAnswered:
description = [[NSString alloc] initWithFormat:@"Call lasted %f seconds", self.callDuration];
break;
case OCTMessageCallEventUnanswered:
description = @"Call unanswered";
break;
}
return description;
}
@end

View File

@ -0,0 +1,36 @@
// 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 "OCTMessageFile.h"
@interface OCTMessageFile ()
@end
@implementation OCTMessageFile
#pragma mark - Public
- (nullable NSString *)filePath
{
return [self.internalFilePath stringByExpandingTildeInPath];
}
- (void)internalSetFilePath:(NSString *)path
{
self.internalFilePath = [path stringByAbbreviatingWithTildeInPath];
}
- (NSString *)description
{
NSString *description = [super description];
const NSUInteger maxSymbols = 3;
NSString *fileName = self.fileName.length > maxSymbols ? ([self.fileName substringToIndex:maxSymbols]) : @"";
return [description stringByAppendingFormat:@"OCTMessageFile with fileName = %@..., fileSize = %llu",
fileName, self.fileSize];
}
@end

View File

@ -0,0 +1,31 @@
// 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 "OCTMessageText.h"
@interface OCTMessageText ()
@end
@implementation OCTMessageText
#pragma mark - Class methods
+ (NSArray *)indexedProperties {
return @[@"msgv3HashHex", @"isDelivered", @"sentPush"];
}
#pragma mark - Public
- (NSString *)description
{
NSString *description = [super description];
const NSUInteger maxSymbols = 3;
NSString *text = self.text.length > maxSymbols ? ([self.text substringToIndex:maxSymbols]) : @"";
return [description stringByAppendingFormat:@"OCTMessageText %@..., length %lu", text, (unsigned long)self.text.length];
}
@end

View File

@ -0,0 +1,30 @@
// 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"
NS_ASSUME_NONNULL_BEGIN
@interface OCTNode : NSObject
@property (copy, nonatomic, readonly, nullable) NSString *ipv4Host;
@property (copy, nonatomic, readonly, nullable) NSString *ipv6Host;
@property (assign, nonatomic, readonly) OCTToxPort udpPort;
@property (copy, nonatomic, readonly) NSArray<NSNumber *> *tcpPorts;
@property (copy, nonatomic, readonly) NSString *publicKey;
- (instancetype)initWithIpv4Host:(nullable NSString *)ipv4Host
ipv6Host:(nullable NSString *)ipv6Host
udpPort:(OCTToxPort)udpPort
tcpPorts:(NSArray<NSNumber *> *)tcpPorts
publicKey:(NSString *)publicKey;
- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,71 @@
// 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 "OCTNode.h"
@implementation OCTNode
#pragma mark - Lifecycle
- (instancetype)initWithIpv4Host:(nullable NSString *)ipv4Host
ipv6Host:(nullable NSString *)ipv6Host
udpPort:(OCTToxPort)udpPort
tcpPorts:(NSArray<NSNumber *> *)tcpPorts
publicKey:(NSString *)publicKey
{
self = [super init];
if (! self) {
return nil;
}
_ipv4Host = [ipv4Host copy];
_ipv6Host = [ipv6Host copy];
_udpPort = udpPort;
_tcpPorts = [tcpPorts copy];
_publicKey = [publicKey copy];
return self;
}
- (BOOL)isEqual:(id)object
{
if (! [object isKindOfClass:[OCTNode class]]) {
return NO;
}
OCTNode *another = object;
return [self compareString:self.ipv4Host with:another.ipv4Host] &&
[self compareString:self.ipv6Host with:another.ipv6Host] &&
(self.udpPort == another.udpPort) &&
[self.tcpPorts isEqual:another.tcpPorts] &&
[self.publicKey isEqual:another.publicKey];
}
- (NSUInteger)hash
{
const NSUInteger prime = 31;
NSUInteger result = 1;
result = prime * result + [self.ipv4Host hash];
result = prime * result + [self.ipv6Host hash];
result = prime * result + self.udpPort;
result = prime * result + [self.tcpPorts hash];
result = prime * result + [self.publicKey hash];
return result;
}
- (BOOL)compareString:(NSString *)first with:(NSString *)second
{
if (first && second) {
return [first isEqual:second];
}
BOOL bothNil = ! first && ! second;
return bothNil;
}
@end

View File

@ -0,0 +1,55 @@
// 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 "OCTObject.h"
@implementation OCTObject
#pragma mark - Class methods
+ (NSString *)primaryKey
{
return NSStringFromSelector(@selector(uniqueIdentifier));
}
+ (NSDictionary *)defaultPropertyValues
{
return @{
NSStringFromSelector(@selector(uniqueIdentifier)) : [[NSUUID UUID] UUIDString],
};
}
+ (NSArray *)requiredProperties
{
return @[NSStringFromSelector(@selector(uniqueIdentifier))];
}
#pragma mark - Public
- (NSString *)description
{
return [NSString stringWithFormat:@"%@ with uniqueIdentifier %@", [self class], self.uniqueIdentifier];
}
- (BOOL)isEqual:(id)object
{
if (object == self) {
return YES;
}
if (! [object isKindOfClass:[self class]]) {
return NO;
}
OCTObject *o = object;
return [self.uniqueIdentifier isEqualToString:o.uniqueIdentifier];
}
- (NSUInteger)hash
{
return [self.uniqueIdentifier hash];
}
@end

View File

@ -0,0 +1,22 @@
// 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 "OCTObject.h"
@interface OCTSettingsStorageObject : OCTObject
@property BOOL bootstrapDidConnect;
/**
* UIImage with avatar of user.
*/
@property NSData *userAvatarData;
/**
* Generic data to be used by user of the library.
* It shouldn't be used by objcTox itself.
*/
@property NSData *genericSettingsData;
@end

View File

@ -0,0 +1,17 @@
// 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 "OCTSettingsStorageObject.h"
@implementation OCTSettingsStorageObject
+ (NSDictionary *)defaultPropertyValues
{
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:[super defaultPropertyValues]];
dict[@"bootstrapDidConnect"] = @NO;
return [dict copy];
}
@end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,23 @@
// 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 "OCTToxAVConstants.h"
@import CoreVideo;
/**
* This class helps with allocating and keeping CVPixelBuffers.
*/
@interface OCTPixelBufferPool : NSObject
- (instancetype)initWithFormat:(OSType)format;
/**
* Grab a pixel buffer from the pool.
* @param bufferRef Reference to the buffer ref.
* @return YES on success, NO otherwise.
*/
- (BOOL)createPixelBuffer:(CVPixelBufferRef *)bufferRef width:(OCTToxAVVideoWidth)width height:(OCTToxAVVideoHeight)height;
@end

View File

@ -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 "OCTPixelBufferPool.h"
#import "OCTLogging.h"
@interface OCTPixelBufferPool ()
@property (nonatomic, assign) CVPixelBufferPoolRef pool;
@property (nonatomic, assign) OSType formatType;
@property (nonatomic, assign) OCTToxAVVideoWidth width;
@property (nonatomic, assign) OCTToxAVVideoHeight height;
@end
@implementation OCTPixelBufferPool
#pragma mark - Lifecycle
- (instancetype)initWithFormat:(OSType)format;
{
self = [super init];
if (! self) {
return nil;
}
_formatType = format;
return self;
}
- (void)dealloc
{
if (self.pool) {
CFRelease(self.pool);
}
}
#pragma mark - Public
- (BOOL)createPixelBuffer:(CVPixelBufferRef *)bufferRef width:(OCTToxAVVideoWidth)width height:(OCTToxAVVideoHeight)height
{
BOOL success = YES;
if (! self.pool) {
success = [self createPoolWithWidth:width height:height format:self.formatType];
}
if ((self.width != width) || (self.height != height)) {
success = [self createPoolWithWidth:width height:height format:self.formatType];
}
if (! success) {
return NO;
}
return [self createPixelBuffer:bufferRef];
}
#pragma mark - Private
- (BOOL)createPoolWithWidth:(OCTToxAVVideoWidth)width height:(OCTToxAVVideoHeight)height format:(OSType)format
{
if (self.pool) {
CFRelease(self.pool);
}
self.width = width;
self.height = height;
NSDictionary *pixelBufferAttributes = @{(id)kCVPixelBufferIOSurfacePropertiesKey : @{},
(id)kCVPixelBufferHeightKey : @(height),
(id)kCVPixelBufferWidthKey : @(width),
(id)kCVPixelBufferPixelFormatTypeKey : @(format)};
CVReturn success = CVPixelBufferPoolCreate(kCFAllocatorDefault,
NULL,
(__bridge CFDictionaryRef)(pixelBufferAttributes),
&_pool);
if (success != kCVReturnSuccess) {
OCTLogWarn(@"failed to create CVPixelBufferPool error:%d", success);
}
return (success == kCVReturnSuccess);
}
- (BOOL)createPixelBuffer:(CVPixelBufferRef *)bufferRef
{
CVReturn success = CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault,
self.pool,
bufferRef);
if (success != kCVReturnSuccess) {
OCTLogWarn(@"Failed to create pixelBuffer error:%d", success);
}
return (success == kCVReturnSuccess);
}
@end

View File

@ -0,0 +1,118 @@
// 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 "OCTView.h"
#import "OCTToxAV.h"
@interface OCTVideoEngine : NSObject
@property (weak, nonatomic) OCTToxAV *toxav;
/**
* Current friend number that video engine should
* process video data to and from.
*/
@property (nonatomic, assign) OCTToxFriendNumber friendNumber;
/**
* This must be called prior to using the video session.
* @param error Pointer to error object.
* @return YES if successful, otherwise NO.
*/
- (BOOL)setupAndReturnError:(NSError **)error;
/**
* Start sending video data.
* This will turn on processIncomingVideo to YES
*/
- (void)startSendingVideo;
/**
* Stop sending video data.
* This will turn off processIncomingVideo to NO
*/
- (void)stopSendingVideo;
/**
* Indicates if the video engine is sending video.
* @return YES if running, NO otherwise.
*/
- (BOOL)isSendingVideo;
/**
* Generate a OCTView with the current incoming video feed.
*/
- (OCTView *)videoFeed;
/**
* Layer of the preview video.
* Layer will be nil if videoSession is not running.
* @param completionBlock Block responsible for using the layer. This
* must not be nil.
*/
- (void)getVideoCallPreview:(void (^)(CALayer *layer))completionBlock;
/**
* Provide video frames to video engine to process.
* @param width Width of the frame in pixels.
* @param height Height of the frame in pixels.
* @param yPlane
* @param uPlane
* @param vPlane Plane data.
* The size of plane data is derived from width and height where
* Y = MAX(width, abs(ystride)) * height,
* U = MAX(width/2, abs(ustride)) * (height/2) and
* V = MAX(width/2, abs(vstride)) * (height/2).
* @param yStride
* @param uStride
* @param vStride Strides data. Strides represent padding for each plane
* that may or may not be present. You must handle strides in
* your image processing code. Strides are negative if the
* image is bottom-up hence why you MUST abs() it when
* calculating plane buffer size.
* @param friendNumber The friend number of the friend who sent an audio frame.
*
*/
- (void)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;
@end
#if ! TARGET_OS_IPHONE
@interface OCTVideoEngine (MacDevice)
/**
* Use a different camera for input.
* @param camera The camera's AVFoundation device ID.
* @param error Pointer to error object.
* @return YES on success, otherwise NO.
*/
- (BOOL)switchToCamera:(NSString *)camera error:(NSError **)error;
@end
#else
@interface OCTVideoEngine (iOSDevice)
/**
* Use a different camera for input.
* @param camera The camera's AVFoundation device ID.
* @param error Pointer to error object.
* @return YES on success, otherwise NO.
*/
- (BOOL)useFrontCamera:(BOOL)front error:(NSError **)error;
@end
#endif

View File

@ -0,0 +1,489 @@
// 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 "OCTVideoEngine.h"
#import "OCTVideoView.h"
#import "OCTPixelBufferPool.h"
#import "OCTManagerConstants.h"
#import "OCTLogging.h"
@import AVFoundation;
static const OSType kPixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
@interface OCTVideoEngine () <AVCaptureVideoDataOutputSampleBufferDelegate>
@property (nonatomic, strong) AVCaptureSession *captureSession;
@property (nonatomic, strong) AVCaptureVideoDataOutput *dataOutput;
@property (nonatomic, strong) dispatch_queue_t processingQueue;
@property (nonatomic, strong) OCTVideoView *videoView;
@property (nonatomic, weak) AVCaptureVideoPreviewLayer *previewLayer;
@property (nonatomic, assign) uint8_t *reusableUChromaPlane;
@property (nonatomic, assign) uint8_t *reusableVChromaPlane;
@property (nonatomic, assign) uint8_t *reusableYChromaPlane;
@property (strong, nonatomic) OCTPixelBufferPool *pixelPool;
@property (nonatomic, assign) NSUInteger sizeOfChromaPlanes;
@property (nonatomic, assign) NSUInteger sizeOfYPlane;
@end
@implementation OCTVideoEngine
#pragma mark - Life cycle
- (instancetype)init
{
self = [super init];
if (! self) {
return nil;
}
OCTLogVerbose(@"init");
// Disabling captureSession for simulator due to bug in iOS 10.
// See https://forums.developer.apple.com/thread/62230
#if ! TARGET_OS_SIMULATOR
_captureSession = [AVCaptureSession new];
_captureSession.sessionPreset = AVCaptureSessionPresetMedium;
if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset640x480]) {
_captureSession.sessionPreset = AVCaptureSessionPreset640x480;
}
#endif
_dataOutput = [AVCaptureVideoDataOutput new];
_processingQueue = dispatch_queue_create("me.dvor.objcTox.OCTVideoEngineQueue", NULL);
_pixelPool = [[OCTPixelBufferPool alloc] initWithFormat:kPixelFormat];
return self;
}
- (void)dealloc
{
if (self.reusableUChromaPlane) {
free(self.reusableUChromaPlane);
}
if (self.reusableVChromaPlane) {
free(self.reusableVChromaPlane);
}
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Public
- (BOOL)setupAndReturnError:(NSError **)error
{
OCTLogVerbose(@"setupAndReturnError");
#if TARGET_OS_IPHONE
AVCaptureDevice *videoCaptureDevice = [self getDeviceForPosition:AVCaptureDevicePositionFront];
#else
AVCaptureDevice *videoCaptureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
#endif
AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoCaptureDevice error:error];
if (! videoInput) {
return NO;
}
if ([self.captureSession canAddInput:videoInput]) {
[self.captureSession addInput:videoInput];
}
self.dataOutput.alwaysDiscardsLateVideoFrames = YES;
self.dataOutput.videoSettings = @{
(NSString *)kCVPixelBufferPixelFormatTypeKey : @(kPixelFormat),
};
[self.dataOutput setSampleBufferDelegate:self queue:self.processingQueue];
[self.captureSession addOutput:self.dataOutput];
AVCaptureConnection *conn = [self.dataOutput connectionWithMediaType:AVMediaTypeVideo];
if (conn.supportsVideoOrientation) {
[self registerOrientationNotification];
[self orientationChanged];
}
return YES;
}
#if ! TARGET_OS_IPHONE
- (BOOL)switchToCamera:(NSString *)camera error:(NSError **)error
{
AVCaptureDevice *dev = nil;
if (! camera) {
dev = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
}
else {
dev = [AVCaptureDevice deviceWithUniqueID:camera];
}
return [self actuallySetCamera:dev error:error];
}
#else
- (BOOL)useFrontCamera:(BOOL)front error:(NSError **)error
{
AVCaptureDevicePosition position = front ? AVCaptureDevicePositionFront : AVCaptureDevicePositionBack;
AVCaptureDevice *dev = [self getDeviceForPosition:position];
return [self actuallySetCamera:dev error:error];
}
#endif
- (BOOL)actuallySetCamera:(AVCaptureDevice *)dev error:(NSError **)error
{
OCTLogVerbose(@"actuallySetCamera: %@", dev);
NSArray *inputs = [self.captureSession inputs];
AVCaptureInput *current = [inputs firstObject];
if ([current isKindOfClass:[AVCaptureDeviceInput class]]) {
AVCaptureDeviceInput *inputDevice = (AVCaptureDeviceInput *)current;
if ([inputDevice.device.uniqueID isEqualToString:dev.uniqueID]) {
return YES;
}
}
for (AVCaptureInput *input in inputs) {
[self.captureSession removeInput:input];
}
AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:dev error:error];
if (! videoInput) {
return NO;
}
if (! [self.captureSession canAddInput:videoInput]) {
return NO;
}
[self.captureSession addInput:videoInput];
[self orientationChanged];
return YES;
}
- (void)startSendingVideo
{
OCTLogVerbose(@"startSendingVideo");
dispatch_async(self.processingQueue, ^{
if ([self isSendingVideo]) {
return;
}
[self.captureSession startRunning];
});
}
- (void)stopSendingVideo
{
OCTLogVerbose(@"stopSendingVideo");
dispatch_async(self.processingQueue, ^{
if (! [self isSendingVideo]) {
return;
}
[self.captureSession stopRunning];
});
}
- (BOOL)isSendingVideo
{
OCTLogVerbose(@"isSendingVideo");
return self.captureSession.isRunning;
}
- (void)getVideoCallPreview:(void (^)(CALayer *))completionBlock
{
NSParameterAssert(completionBlock);
OCTLogVerbose(@"videoCallPreview");
dispatch_async(self.processingQueue, ^{
AVCaptureVideoPreviewLayer *previewLayer = self.previewLayer;
if (! self.previewLayer) {
previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.captureSession];
}
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(previewLayer);
});
self.previewLayer = previewLayer;
});
}
- (OCTView *)videoFeed;
{
OCTLogVerbose(@"videoFeed");
OCTVideoView *feed = self.videoView;
if (! feed) {
feed = [OCTVideoView view];
self.videoView = feed;
}
return feed;
}
- (void)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
{
if (! self.videoView) {
return;
}
if (! yPlane) {
return;
}
if (! uPlane) {
return;
}
if (! vPlane) {
return;
}
size_t yBytesPerRow = MIN(width, abs(yStride));
size_t uvBytesPerRow = MIN(width / 2, abs(uStride));
/**
* Create pixel buffers and copy YUV planes over
*/
CVPixelBufferRef bufferRef = NULL;
if (! [self.pixelPool createPixelBuffer:&bufferRef width:width height:height]) {
return;
}
CVPixelBufferLockBaseAddress(bufferRef, 0);
OCTToxAVPlaneData *ySource = yPlane;
// if stride is negative, start reading from the left of the last row
if (yStride < 0) {
ySource = ySource + ((-yStride) * (height - 1));
}
uint8_t *yDestinationPlane = CVPixelBufferGetBaseAddressOfPlane(bufferRef, 0);
size_t yDestinationStride = CVPixelBufferGetBytesPerRowOfPlane(bufferRef, 0);
/* Copy yPlane data */
for (size_t yHeight = 0; yHeight < height; yHeight++) {
memcpy(yDestinationPlane, ySource, yBytesPerRow);
ySource += yStride;
yDestinationPlane += yDestinationStride;
}
/* Interweave U and V */
uint8_t *uvDestinationPlane = CVPixelBufferGetBaseAddressOfPlane(bufferRef, 1);
size_t uvDestinationStride = CVPixelBufferGetBytesPerRowOfPlane(bufferRef, 1);
OCTToxAVPlaneData *uSource = uPlane;
if (uStride < 0) {
uSource = uSource + ((-uStride) * ((height / 2) - 1));
}
OCTToxAVPlaneData *vSource = vPlane;
if (vStride < 0) {
vSource = vSource + ((-vStride) * ((height / 2) - 1));
}
for (size_t yHeight = 0; yHeight < height / 2; yHeight++) {
for (size_t index = 0; index < uvBytesPerRow; index++) {
uvDestinationPlane[index * 2] = uSource[index];
uvDestinationPlane[(index * 2) + 1] = vSource[index];
}
uvDestinationPlane += uvDestinationStride;
uSource += uStride;
vSource += vStride;
}
CVPixelBufferUnlockBaseAddress(bufferRef, 0);
dispatch_async(self.processingQueue, ^{
/* Create Core Image */
CIImage *coreImage = [CIImage imageWithCVPixelBuffer:bufferRef];
CVPixelBufferRelease(bufferRef);
self.videoView.image = coreImage;
});
}
#pragma mark - Buffer Delegate
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
if (! imageBuffer) {
return;
}
CVPixelBufferLockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
size_t yHeight = CVPixelBufferGetHeightOfPlane(imageBuffer, 0);
size_t yBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0);
size_t yStride = MAX(CVPixelBufferGetWidthOfPlane(imageBuffer, 0), yBytesPerRow);
size_t uvHeight = CVPixelBufferGetHeightOfPlane(imageBuffer, 1);
size_t uvBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 1);
size_t uvStride = MAX(CVPixelBufferGetWidthOfPlane(imageBuffer, 1), uvBytesPerRow);
size_t ySize = yBytesPerRow * yHeight;
size_t numberOfElementsForChroma = uvBytesPerRow * uvHeight / 2;
/**
* Recreate the buffers if the original ones are too small
*/
if (numberOfElementsForChroma > self.sizeOfChromaPlanes) {
if (self.reusableUChromaPlane) {
free(self.reusableUChromaPlane);
}
if (self.reusableVChromaPlane) {
free(self.reusableVChromaPlane);
}
self.reusableUChromaPlane = malloc(numberOfElementsForChroma * sizeof(OCTToxAVPlaneData));
self.reusableVChromaPlane = malloc(numberOfElementsForChroma * sizeof(OCTToxAVPlaneData));
self.sizeOfChromaPlanes = numberOfElementsForChroma;
}
if (ySize > self.sizeOfYPlane) {
if (self.reusableYChromaPlane) {
free(self.reusableYChromaPlane);
}
self.reusableYChromaPlane = malloc(ySize * sizeof(OCTToxAVPlaneData));
self.sizeOfYPlane = ySize;
}
/**
* Copy the Y plane data while skipping stride
*/
OCTToxAVPlaneData *yPlane = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);
uint8_t *yDestination = self.reusableYChromaPlane;
for (size_t i = 0; i < yHeight; i++) {
memcpy(yDestination, yPlane, yBytesPerRow);
yPlane += yStride;
yDestination += yBytesPerRow;
}
/**
* Deinterleaved the UV [uvuvuvuv] planes and place them to in the reusable arrays
*/
OCTToxAVPlaneData *uvPlane = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1);
uint8_t *uDestination = self.reusableUChromaPlane;
uint8_t *vDestination = self.reusableVChromaPlane;
for (size_t height = 0; height < uvHeight; height++) {
for (size_t i = 0; i < uvBytesPerRow; i += 2) {
uDestination[i / 2] = uvPlane[i];
vDestination[i / 2] = uvPlane[i + 1];
}
uvPlane += uvStride;
uDestination += uvBytesPerRow / 2;
vDestination += uvBytesPerRow / 2;
}
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
uDestination = nil;
vDestination = nil;
NSError *error;
if (! [self.toxav sendVideoFrametoFriend:self.friendNumber
width:(OCTToxAVVideoWidth)yBytesPerRow
height:(OCTToxAVVideoHeight)yHeight
yPlane:self.reusableYChromaPlane
uPlane:self.reusableUChromaPlane
vPlane:self.reusableVChromaPlane
error:&error]) {
OCTLogWarn(@"error:%@ width:%zu height:%zu", error, yBytesPerRow, yHeight);
}
}
#pragma mark - Private
- (AVCaptureDevice *)getDeviceForPosition:(AVCaptureDevicePosition)position
{
OCTLogVerbose(@"getDeviceForPosition");
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
for (AVCaptureDevice *device in devices) {
if ([device position] == position) {
return device;
}
}
return nil;
}
- (void)registerOrientationNotification
{
#if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(orientationChanged)
name:UIDeviceOrientationDidChangeNotification
object:nil];
#endif
}
- (void)orientationChanged
{
#if TARGET_OS_IPHONE
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
AVCaptureConnection *conn = [self.dataOutput connectionWithMediaType:AVMediaTypeVideo];
AVCaptureVideoOrientation orientation;
switch (deviceOrientation) {
case UIInterfaceOrientationPortraitUpsideDown:
orientation = AVCaptureVideoOrientationPortraitUpsideDown;
break;
case UIDeviceOrientationPortrait:
orientation = AVCaptureVideoOrientationPortrait;
break;
/* Landscapes are reversed, otherwise for some reason the video will be upside down */
case UIDeviceOrientationLandscapeLeft:
orientation = AVCaptureVideoOrientationLandscapeRight;
break;
case UIDeviceOrientationLandscapeRight:
orientation = AVCaptureVideoOrientationLandscapeLeft;
break;
default:
return;
}
conn.videoOrientation = orientation;
self.previewLayer.connection.videoOrientation = orientation;
#endif
}
@end

View File

@ -0,0 +1,22 @@
// 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 "OCTView.h"
@import GLKit;
#if TARGET_OS_IPHONE
@interface OCTVideoView : GLKView
#else
@interface OCTVideoView : NSOpenGLView
#endif
@property (strong, nonatomic) CIImage *image;
/**
* Allocs and calls the platform-specific initializers.
*/
+ (instancetype)view;
@end

View File

@ -0,0 +1,100 @@
// 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 "OCTVideoView.h"
#import "OCTManagerConstants.h"
#import "OCTLogging.h"
@import Foundation;
@import AVFoundation;
@interface OCTVideoView ()
@property (strong, nonatomic) CIContext *coreImageContext;
@end
@implementation OCTVideoView
+ (instancetype)view
{
#if TARGET_OS_IPHONE
OCTVideoView *videoView = [[self alloc] initWithFrame:CGRectZero];
#else
OCTVideoView *videoView = [[self alloc] initWithFrame:CGRectZero pixelFormat:[self defaultPixelFormat]];
#endif
[videoView finishInitializing];
return videoView;
}
- (void)dealloc
{
OCTLogVerbose(@"dealloc");
}
- (void)finishInitializing
{
#if TARGET_OS_IPHONE
__weak OCTVideoView *weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
OCTVideoView *strongSelf = weakSelf;
strongSelf.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
strongSelf.coreImageContext = [CIContext contextWithEAGLContext:strongSelf.context];
});
self.enableSetNeedsDisplay = NO;
#endif
}
- (void)setImage:(CIImage *)image
{
_image = image;
#if TARGET_OS_IPHONE
[self display];
#else
[self setNeedsDisplay:YES];
#endif
}
#if ! TARGET_OS_IPHONE
// OS X: we need to correct the viewport when the view size changes
- (void)reshape
{
glViewport(0, 0, self.bounds.size.width, self.bounds.size.height);
}
#endif
- (void)drawRect:(CGRect)rect
{
#if TARGET_OS_IPHONE
if (self.image) {
glClearColor(0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
CGRect destRect = AVMakeRectWithAspectRatioInsideRect(self.image.extent.size, rect);
float screenscale = self.window.screen.scale;
destRect = CGRectApplyAffineTransform(destRect, CGAffineTransformMakeScale(screenscale, screenscale));
[self.coreImageContext drawImage:self.image inRect:destRect fromRect:self.image.extent];
}
#else
[self.openGLContext makeCurrentContext];
if (self.image) {
CIContext *ctx = [CIContext contextWithCGLContext:self.openGLContext.CGLContextObj pixelFormat:self.openGLContext.pixelFormat.CGLPixelFormatObj colorSpace:nil options:nil];
// The GL coordinate system goes from -1 to 1 on all axes by default.
// We didn't set a matrix so use that instead of bounds.
[ctx drawImage:self.image inRect:(CGRect) {-1, -1, 2, 2} fromRect:self.image.extent];
}
else {
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
}
glFlush();
#endif
}
@end

View File

@ -0,0 +1,25 @@
// 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 "DDLog.h"
#undef LOG_LEVEL_DEF
#define LOG_LEVEL_DEF LOG_LEVEL_VERBOSE
#define OCTLogError(frmt, ...) DDLogError((@"<%@ %p> " frmt), [self class], self, ## __VA_ARGS__)
#define OCTLogWarn(frmt, ...) DDLogWarn((@"<%@ %p> " frmt), [self class], self, ## __VA_ARGS__)
#define OCTLogInfo(frmt, ...) DDLogInfo((@"<%@ %p> " frmt), [self class], self, ## __VA_ARGS__)
#define OCTLogDebug(frmt, ...) DDLogDebug((@"<%@ %p> " frmt), [self class], self, ## __VA_ARGS__)
#define OCTLogVerbose(frmt, ...) DDLogVerbose((@"<%@ %p> " frmt), [self class], self, ## __VA_ARGS__)
#define OCTLogCError(frmt, obj, ...) DDLogCError((@"<%@ %p> " frmt), [obj class], obj, ## __VA_ARGS__)
#define OCTLogCWarn(frmt, obj, ...) DDLogCWarn((@"<%@ %p> " frmt), [obj class], obj, ## __VA_ARGS__)
#define OCTLogCInfo(frmt, obj, ...) DDLogCInfo((@"<%@ %p> " frmt), [obj class], obj, ## __VA_ARGS__)
#define OCTLogCDebug(frmt, obj, ...) DDLogCDebug((@"<%@ %p> " frmt), [obj class], obj, ## __VA_ARGS__)
#define OCTLogCVerbose(frmt, obj, ...) DDLogCVerbose((@"<%@ %p> " frmt), [obj class], obj, ## __VA_ARGS__)
#define OCTLogCCError(frmt, ...) DDLogCError((frmt), ## __VA_ARGS__)
#define OCTLogCCWarn(frmt, ...) DDLogCWarn((frmt), ## __VA_ARGS__)
#define OCTLogCCInfo(frmt, ...) DDLogCInfo((frmt), ## __VA_ARGS__)
#define OCTLogCCDebug(frmt, ...) DDLogCDebug((frmt), ## __VA_ARGS__)
#define OCTLogCCVerbose(frmt, ...) DDLogCVerbose((frmt), ## __VA_ARGS__)

View File

@ -0,0 +1,63 @@
// 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 "OCTTox.h"
#import <toxcore/tox.h>
/**
* Tox functions
*/
extern void (*_tox_self_get_public_key)(const Tox *tox, uint8_t *public_key);
/**
* Callbacks
*/
tox_log_cb logCallback;
tox_self_connection_status_cb connectionStatusCallback;
tox_friend_name_cb friendNameCallback;
tox_friend_status_message_cb friendStatusMessageCallback;
tox_friend_status_cb friendStatusCallback;
tox_friend_connection_status_cb friendConnectionStatusCallback;
tox_friend_typing_cb friendTypingCallback;
tox_friend_read_receipt_cb friendReadReceiptCallback;
tox_friend_request_cb friendRequestCallback;
tox_friend_message_cb friendMessageCallback;
tox_friend_lossless_packet_cb friendLosslessPacketCallback;
tox_file_recv_control_cb fileReceiveControlCallback;
tox_file_chunk_request_cb fileChunkRequestCallback;
tox_file_recv_cb fileReceiveCallback;
tox_file_recv_chunk_cb fileReceiveChunkCallback;
@interface OCTTox (Private)
@property (assign, nonatomic) Tox *tox;
- (OCTToxUserStatus)userStatusFromCUserStatus:(TOX_USER_STATUS)cStatus;
- (OCTToxConnectionStatus)userConnectionStatusFromCUserStatus:(TOX_CONNECTION)cStatus;
- (OCTToxMessageType)messageTypeFromCMessageType:(TOX_MESSAGE_TYPE)cType;
- (OCTToxFileControl)fileControlFromCFileControl:(TOX_FILE_CONTROL)cControl;
- (BOOL)fillError:(NSError **)error withCErrorInit:(TOX_ERR_NEW)cError;
- (BOOL)fillError:(NSError **)error withCErrorBootstrap:(TOX_ERR_BOOTSTRAP)cError;
- (BOOL)fillError:(NSError **)error withCErrorFriendAdd:(TOX_ERR_FRIEND_ADD)cError;
- (BOOL)fillError:(NSError **)error withCErrorFriendDelete:(TOX_ERR_FRIEND_DELETE)cError;
- (BOOL)fillError:(NSError **)error withCErrorFriendByPublicKey:(TOX_ERR_FRIEND_BY_PUBLIC_KEY)cError;
- (BOOL)fillError:(NSError **)error withCErrorFriendGetPublicKey:(TOX_ERR_FRIEND_GET_PUBLIC_KEY)cError;
- (BOOL)fillError:(NSError **)error withCErrorSetInfo:(TOX_ERR_SET_INFO)cError;
- (BOOL)fillError:(NSError **)error withCErrorFriendGetLastOnline:(TOX_ERR_FRIEND_GET_LAST_ONLINE)cError;
- (BOOL)fillError:(NSError **)error withCErrorFriendQuery:(TOX_ERR_FRIEND_QUERY)cError;
- (BOOL)fillError:(NSError **)error withCErrorSetTyping:(TOX_ERR_SET_TYPING)cError;
- (BOOL)fillError:(NSError **)error withCErrorFriendSendMessage:(TOX_ERR_FRIEND_SEND_MESSAGE)cError;
- (BOOL)fillError:(NSError **)error withCErrorFileControl:(TOX_ERR_FILE_CONTROL)cError;
- (BOOL)fillError:(NSError **)error withCErrorFileSeek:(TOX_ERR_FILE_SEEK)cError;
- (BOOL)fillError:(NSError **)error withCErrorFileGet:(TOX_ERR_FILE_GET)cError;
- (BOOL)fillError:(NSError **)error withCErrorFileSend:(TOX_ERR_FILE_SEND)cError;
- (BOOL)fillError:(NSError **)error withCErrorFileSendChunk:(TOX_ERR_FILE_SEND_CHUNK)cError;
+ (NSError *)createErrorWithCode:(NSUInteger)code
description:(NSString *)description
failureReason:(NSString *)failureReason;
- (struct Tox_Options) cToxOptionsFromOptions:(OCTToxOptions *)options;
+ (NSString *)binToHexString:(uint8_t *)bin length:(NSUInteger)length;
+ (uint8_t *)hexStringToBin:(NSString *)string;
@end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,51 @@
// 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 "OCTToxAV.h"
#import <toxcore/toxav/toxav.h>
/**
* ToxAV functions
*/
extern ToxAV *(*_toxav_new)(Tox *tox, TOXAV_ERR_NEW *error);
extern uint32_t (*_toxav_iteration_interval)(const ToxAV *toxAV);
extern void (*_toxav_iterate)(ToxAV *toxAV);
extern void (*_toxav_kill)(ToxAV *toxAV);
extern bool (*_toxav_call)(ToxAV *toxAV, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, TOXAV_ERR_CALL *error);
extern bool (*_toxav_answer)(ToxAV *toxAV, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, TOXAV_ERR_ANSWER *error);
extern bool (*_toxav_call_control)(ToxAV *toxAV, uint32_t friend_number, TOXAV_CALL_CONTROL control, TOXAV_ERR_CALL_CONTROL *error);
extern bool (*_toxav_audio_set_bit_rate)(ToxAV *av, uint32_t friend_number, uint32_t bit_rate, TOXAV_ERR_BIT_RATE_SET *error);
extern bool (*_toxav_video_set_bit_rate)(ToxAV *av, uint32_t friend_number, uint32_t bit_rate, TOXAV_ERR_BIT_RATE_SET *error);
extern bool (*_toxav_audio_send_frame)(ToxAV *toxAV, uint32_t friend_number, const int16_t *pcm, size_t sample_count, uint8_t channels, uint32_t sampling_rate, TOXAV_ERR_SEND_FRAME *error);
extern bool (*_toxav_video_send_frame)(ToxAV *toxAV, uint32_t friend_number, uint16_t width, uint16_t height, const uint8_t *y, const uint8_t *u, const uint8_t *v, TOXAV_ERR_SEND_FRAME *error);
/**
* Callbacks
*/
toxav_call_cb callIncomingCallback;
toxav_call_state_cb callStateCallback;
toxav_audio_bit_rate_cb audioBitRateStatusCallback;
toxav_video_bit_rate_cb videoBitRateStatusCallback;
toxav_audio_receive_frame_cb receiveAudioFrameCallback;
toxav_video_receive_frame_cb receiveVideoFrameCallback;
@interface OCTToxAV (Private)
@property (assign, nonatomic) ToxAV *toxAV;
- (BOOL)fillError:(NSError **)error withCErrorInit:(TOXAV_ERR_NEW)cError;
- (BOOL)fillError:(NSError **)error withCErrorCall:(TOXAV_ERR_CALL)cError;
- (BOOL)fillError:(NSError **)error withCErrorAnswer:(TOXAV_ERR_ANSWER)cError;
- (BOOL)fillError:(NSError **)error withCErrorControl:(TOXAV_ERR_CALL_CONTROL)cError;
- (BOOL)fillError:(NSError **)error withCErrorSetBitRate:(TOXAV_ERR_BIT_RATE_SET)cError;
- (BOOL)fillError:(NSError **)error withCErrorSendFrame:(TOXAV_ERR_SEND_FRAME)cError;
- (NSError *)createErrorWithCode:(NSUInteger)code
description:(NSString *)description
failureReason:(NSString *)failureReason;
@end

View File

@ -0,0 +1,648 @@
// 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 "OCTTox+Private.h"
#import "OCTToxAV+Private.h"
#import "OCTLogging.h"
ToxAV *(*_toxav_new)(Tox *tox, TOXAV_ERR_NEW *error);
uint32_t (*_toxav_iteration_interval)(const ToxAV *toxAV);
void (*_toxav_iterate)(ToxAV *toxAV);
void (*_toxav_kill)(ToxAV *toxAV);
bool (*_toxav_call)(ToxAV *toxAV, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, TOXAV_ERR_CALL *error);
bool (*_toxav_answer)(ToxAV *toxAV, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, TOXAV_ERR_ANSWER *error);
bool (*_toxav_call_control)(ToxAV *toxAV, uint32_t friend_number, TOXAV_CALL_CONTROL control, TOXAV_ERR_CALL_CONTROL *error);
bool (*_toxav_audio_set_bit_rate)(ToxAV *av, uint32_t friend_number, uint32_t bit_rate, TOXAV_ERR_BIT_RATE_SET *error);
bool (*_toxav_video_set_bit_rate)(ToxAV *av, uint32_t friend_number, uint32_t bit_rate, TOXAV_ERR_BIT_RATE_SET *error);
bool (*_toxav_audio_send_frame)(ToxAV *toxAV, uint32_t friend_number, const int16_t *pcm, size_t sample_count, uint8_t channels, uint32_t sampling_rate, TOXAV_ERR_SEND_FRAME *error);
bool (*_toxav_video_send_frame)(ToxAV *toxAV, uint32_t friend_number, uint16_t width, uint16_t height, const uint8_t *y, const uint8_t *u, const uint8_t *v, TOXAV_ERR_SEND_FRAME *error);
@interface OCTToxAV ()
@property (assign, nonatomic) ToxAV *toxAV;
@property (strong, nonatomic) dispatch_source_t timer;
@property (assign, nonatomic) uint64_t previousIterate;
@end
@implementation OCTToxAV
#pragma mark - Lifecycle
- (instancetype)initWithTox:(OCTTox *)tox error:(NSError **)error
{
self = [super init];
if (! self) {
return nil;
}
OCTLogVerbose(@"init called");
[self setupCFunctions];
TOXAV_ERR_NEW cError;
_toxAV = _toxav_new(tox.tox, &cError);
[self fillError:error withCErrorInit:cError];
[self setupCallbacks];
return self;
}
- (void)start
{
OCTLogVerbose(@"start method called");
@synchronized(self) {
if (self.timer) {
OCTLogWarn(@"already started");
return;
}
dispatch_queue_t queue = dispatch_queue_create("me.dvor.objcTox.OCTToxAVQueue", NULL);
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
[self updateTimerIntervalIfNeeded];
__weak OCTToxAV *weakSelf = self;
dispatch_source_set_event_handler(self.timer, ^{
OCTToxAV *strongSelf = weakSelf;
if (! strongSelf) {
return;
}
_toxav_iterate(strongSelf.toxAV);
[strongSelf updateTimerIntervalIfNeeded];
});
dispatch_resume(self.timer);
}
OCTLogInfo(@"started");
}
- (void)stop
{
OCTLogVerbose(@"stop method called");
@synchronized(self) {
if (! self.timer) {
OCTLogWarn(@"toxav isn't running, nothing to stop");
return;
}
dispatch_source_cancel(self.timer);
self.timer = nil;
}
OCTLogInfo(@"stopped");
}
- (void)dealloc
{
[self stop];
_toxav_kill(self.toxAV);
OCTLogVerbose(@"dealloc called, toxav killed");
}
#pragma mark - Call Methods
- (BOOL)callFriendNumber:(OCTToxFriendNumber)friendNumber audioBitRate:(OCTToxAVAudioBitRate)audioBitRate videoBitRate:(OCTToxAVVideoBitRate)videoBitRate error:(NSError **)error
{
TOXAV_ERR_CALL cError;
BOOL status = _toxav_call(self.toxAV, friendNumber, audioBitRate, videoBitRate, &cError);
[self fillError:error withCErrorCall:cError];
return status;
}
- (BOOL)answerIncomingCallFromFriend:(OCTToxFriendNumber)friendNumber audioBitRate:(OCTToxAVAudioBitRate)audioBitRate videoBitRate:(OCTToxAVVideoBitRate)videoBitrate error:(NSError **)error
{
TOXAV_ERR_ANSWER cError;
BOOL status = _toxav_answer(self.toxAV, friendNumber, audioBitRate, videoBitrate, &cError);
[self fillError:error withCErrorAnswer:cError];
return status;
}
- (BOOL)sendCallControl:(OCTToxAVCallControl)control toFriendNumber:(OCTToxFriendNumber)friendNumber error:(NSError **)error
{
TOXAV_CALL_CONTROL cControl;
switch (control) {
case OCTToxAVCallControlResume:
cControl = TOXAV_CALL_CONTROL_RESUME;
break;
case OCTToxAVCallControlPause:
cControl = TOXAV_CALL_CONTROL_PAUSE;
break;
case OCTToxAVCallControlCancel:
cControl = TOXAV_CALL_CONTROL_CANCEL;
break;
case OCTToxAVCallControlMuteAudio:
cControl = TOXAV_CALL_CONTROL_MUTE_AUDIO;
break;
case OCTToxAVCallControlUnmuteAudio:
cControl = TOXAV_CALL_CONTROL_UNMUTE_AUDIO;
break;
case OCTToxAVCallControlHideVideo:
cControl = TOXAV_CALL_CONTROL_HIDE_VIDEO;
break;
case OCTToxAVCallControlShowVideo:
cControl = TOXAV_CALL_CONTROL_SHOW_VIDEO;
break;
}
TOXAV_ERR_CALL_CONTROL cError;
BOOL status = _toxav_call_control(self.toxAV, friendNumber, cControl, &cError);
[self fillError:error withCErrorControl:cError];
return status;
}
#pragma mark - Controlling bit rates
- (BOOL)setAudioBitRate:(OCTToxAVAudioBitRate)bitRate force:(BOOL)force forFriend:(OCTToxFriendNumber)friendNumber error:(NSError **)error
{
TOXAV_ERR_BIT_RATE_SET cError;
BOOL status = _toxav_audio_set_bit_rate(self.toxAV, friendNumber, bitRate, &cError);
[self fillError:error withCErrorSetBitRate:cError];
OCTLogVerbose(@"setAudioBitRate:%lu, force:%d, friend:%d", (long)bitRate, force, friendNumber);
return status;
}
- (BOOL)setVideoBitRate:(OCTToxAVVideoBitRate)bitRate force:(BOOL)force forFriend:(OCTToxFriendNumber)friendNumber error:(NSError **)error
{
TOXAV_ERR_BIT_RATE_SET cError;
BOOL status = _toxav_video_set_bit_rate(self.toxAV, friendNumber, bitRate, &cError);
[self fillError:error withCErrorSetBitRate:cError];
return status;
}
#pragma mark - Sending frames
- (BOOL)sendAudioFrame:(OCTToxAVPCMData *)pcm sampleCount:(OCTToxAVSampleCount)sampleCount
channels:(OCTToxAVChannels)channels sampleRate:(OCTToxAVSampleRate)sampleRate
toFriend:(OCTToxFriendNumber)friendNumber error:(NSError **)error
{
// TOXAUDIO: -outgoing-audio-
TOXAV_ERR_SEND_FRAME cError;
BOOL status = _toxav_audio_send_frame(self.toxAV, friendNumber,
pcm, sampleCount,
channels, sampleRate, &cError);
[self fillError:error withCErrorSendFrame:cError];
return status;
}
- (BOOL)sendVideoFrametoFriend:(OCTToxFriendNumber)friendNumber
width:(OCTToxAVVideoWidth)width height:(OCTToxAVVideoHeight)height
yPlane:(OCTToxAVPlaneData *)yPlane uPlane:(OCTToxAVPlaneData *)uPlane
vPlane:(OCTToxAVPlaneData *)vPlane
error:(NSError **)error
{
TOXAV_ERR_SEND_FRAME cError;
BOOL status = _toxav_video_send_frame(self.toxAV, friendNumber, width, height, yPlane, uPlane, vPlane, &cError);
[self fillError:error withCErrorSendFrame:cError];
return status;
}
#pragma mark - Private
- (void)setupCFunctions
{
_toxav_new = toxav_new;
_toxav_iteration_interval = toxav_iteration_interval;
_toxav_iterate = toxav_iterate;
_toxav_kill = toxav_kill;
_toxav_call = toxav_call;
_toxav_answer = toxav_answer;
_toxav_call_control = toxav_call_control;
_toxav_audio_set_bit_rate = toxav_audio_set_bit_rate;
_toxav_video_set_bit_rate = toxav_video_set_bit_rate;
_toxav_audio_send_frame = toxav_audio_send_frame;
_toxav_video_send_frame = toxav_video_send_frame;
}
- (void)setupCallbacks
{
toxav_callback_call(_toxAV, callIncomingCallback, (__bridge void *)(self));
toxav_callback_call_state(_toxAV, callStateCallback, (__bridge void *)(self));
toxav_callback_audio_bit_rate(_toxAV, audioBitRateStatusCallback, (__bridge void *)(self));
toxav_callback_video_bit_rate(_toxAV, videoBitRateStatusCallback, (__bridge void *)(self));
toxav_callback_audio_receive_frame(_toxAV, receiveAudioFrameCallback, (__bridge void *)(self));
toxav_callback_video_receive_frame(_toxAV, receiveVideoFrameCallback, (__bridge void *)(self));
}
- (BOOL)fillError:(NSError **)error withCErrorInit:(TOXAV_ERR_NEW)cError
{
if (! error || (cError == TOXAV_ERR_NEW_OK)) {
return NO;
}
OCTToxAVErrorInitCode code = OCTToxAVErrorInitCodeUnknown;
NSString *description = @"Cannot initialize ToxAV";
NSString *failureReason = nil;
switch (cError) {
case TOXAV_ERR_NEW_OK:
NSAssert(NO, @"We shouldn't be here!");
break;
case TOXAV_ERR_NEW_NULL:
code = OCTToxAVErrorInitNULL;
failureReason = @"One of the arguments to the function was NULL when it was not expected.";
break;
case TOXAV_ERR_NEW_MALLOC:
code = OCTToxAVErrorInitCodeMemoryError;
failureReason = @"Memory allocation failure while trying to allocate structures required for the A/V session.";
break;
case TOXAV_ERR_NEW_MULTIPLE:
code = OCTToxAVErrorInitMultiple;
failureReason = @"Attempted to create a second session for the same Tox instance.";
break;
}
*error = [self createErrorWithCode:code description:description failureReason:failureReason];
return YES;
}
- (BOOL)fillError:(NSError **)error withCErrorCall:(TOXAV_ERR_CALL)cError
{
if (! error || (cError == TOXAV_ERR_CALL_OK)) {
return NO;
}
OCTToxAVErrorCall code = OCTToxAVErrorCallUnknown;
NSString *description = @"Could not make call";
NSString *failureReason = nil;
switch (cError) {
case TOXAV_ERR_CALL_OK:
NSAssert(NO, @"We shouldn't be here!");
break;
case TOXAV_ERR_CALL_MALLOC:
code = OCTToxAVErrorCallMalloc;
failureReason = @"A resource allocation error occured while trying to create the structures required for the call.";
break;
case TOXAV_ERR_CALL_SYNC:
code = OCTToxAVErrorCallSync;
failureReason = @"Synchronization error occurred.";
break;
case TOXAV_ERR_CALL_FRIEND_NOT_FOUND:
code = OCTToxAVErrorCallFriendNotFound;
failureReason = @"The friend number did not designate a valid friend.";
break;
case TOXAV_ERR_CALL_FRIEND_NOT_CONNECTED:
code = OCTToxAVErrorCallFriendNotConnected;
failureReason = @"The friend was valid, but not currently connected";
break;
case TOXAV_ERR_CALL_FRIEND_ALREADY_IN_CALL:
code = OCTToxAVErrorCallAlreadyInCall;
failureReason = @"Attempted to call a friend while already in an audio or video call with them.";
break;
case TOXAV_ERR_CALL_INVALID_BIT_RATE:
code = OCTToxAVErrorCallInvalidBitRate;
failureReason = @"Audio or video bit rate is invalid";
break;
}
*error = [self createErrorWithCode:code description:description failureReason:failureReason];
return YES;
}
- (BOOL)fillError:(NSError **)error withCErrorAnswer:(TOXAV_ERR_ANSWER)cError
{
if (! error || (cError == TOXAV_ERR_ANSWER_OK)) {
return NO;
}
OCTToxAVErrorAnswer code = OCTToxAVErrorAnswerUnknown;
NSString *description = @"Could not answer call";
NSString *failureReason = nil;
switch (cError) {
case TOXAV_ERR_ANSWER_OK:
NSAssert(NO, @"We shouldn't be here!");
break;
case TOXAV_ERR_ANSWER_SYNC:
code = OCTToxAVErrorAnswerSync;
break;
case TOXAV_ERR_ANSWER_CODEC_INITIALIZATION:
code = OCTToxAVErrorAnswerCodecInitialization;
break;
case TOXAV_ERR_ANSWER_FRIEND_NOT_CALLING:
code = OCTToxAVErrorAnswerFriendNotCalling;
break;
case TOXAV_ERR_ANSWER_FRIEND_NOT_FOUND:
code = OCTToxAVErrorAnswerFriendNotFound;
break;
case TOXAV_ERR_ANSWER_INVALID_BIT_RATE:
code = OCTToxAVErrorAnswerInvalidBitRate;
break;
}
*error = [self createErrorWithCode:code description:description failureReason:failureReason];
return YES;
}
- (BOOL)fillError:(NSError **)error withCErrorControl:(TOXAV_ERR_CALL_CONTROL)cError
{
if (! error || (cError == TOXAV_ERR_CALL_CONTROL_OK)) {
return NO;
}
OCTToxErrorCallControl code = OCTToxAVErrorControlUnknown;
NSString *description = @"Unable set control";
NSString *failureReason = nil;
switch (cError) {
case TOXAV_ERR_CALL_CONTROL_OK:
NSAssert(NO, @"We shouldn't be here!");
break;
case TOXAV_ERR_CALL_CONTROL_SYNC:
code = OCTToxAVErrorControlSync;
failureReason = @"Synchronization error occurred.";
break;
case TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_FOUND:
code = OCTToxAVErrorControlFriendNotFound;
failureReason = @"The friend number passed did not designate a valid friend.";
break;
case TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_IN_CALL:
code = OCTToxAVErrorControlFriendNotInCall;
failureReason = @"This client is currently not in a call with the friend. Before the call is answered, only CANCEL is a valid control.";
break;
case TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION:
code = OCTToxAVErrorControlInvaldTransition;
failureReason = @"Happens if user tried to pause an already paused call or if trying to resume a call that is not paused.";
break;
}
*error = [self createErrorWithCode:code description:description failureReason:failureReason];
return YES;
}
- (BOOL)fillError:(NSError **)error withCErrorSetBitRate:(TOXAV_ERR_BIT_RATE_SET)cError
{
if (! error || (cError == TOXAV_ERR_BIT_RATE_SET_OK)) {
return NO;
}
OCTToxAVErrorSetBitRate code = OCTToxAVErrorSetBitRateUnknown;
NSString *description = @"Unable to set audio/video bitrate";
NSString *failureReason = nil;
switch (cError) {
case TOXAV_ERR_BIT_RATE_SET_OK:
NSAssert(NO, @"We shouldn't be here!");
break;
case TOXAV_ERR_BIT_RATE_SET_SYNC:
code = OCTToxAVErrorSetBitRateSync;
failureReason = @"Synchronization error occurred.";
break;
case TOXAV_ERR_BIT_RATE_SET_INVALID_BIT_RATE:
code = OCTToxAVErrorSetBitRateInvalidBitRate;
failureReason = @"The bit rate passed was not one of the supported values.";
break;
case TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_FOUND:
code = OCTToxAVErrorSetBitRateFriendNotFound;
failureReason = @"The friend number passed did not designate a valid friend";
break;
case TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_IN_CALL:
code = OCTToxAVErrorSetBitRateFriendNotInCall;
failureReason = @"This client is currently not in a call with the friend";
break;
}
*error = [self createErrorWithCode:code description:description failureReason:failureReason];
return YES;
}
- (BOOL)fillError:(NSError **)error withCErrorSendFrame:(TOXAV_ERR_SEND_FRAME)cError
{
if (! error || (cError == TOXAV_ERR_SEND_FRAME_OK)) {
return NO;
}
OCTToxAVErrorSendFrame code = OCTToxAVErrorSendFrameUnknown;
NSString *description = @"Failed to send audio/video frame";
NSString *failureReason = @"Unable to sending audio/video frame";
switch (cError) {
case TOXAV_ERR_SEND_FRAME_OK:
NSAssert(NO, @"We shouldn't be here!");
break;
case TOXAV_ERR_SEND_FRAME_NULL:
code = OCTToxAVErrorSendFrameNull;
failureReason = @"In case of video, one of Y, U, or V was NULL. In case of audio, the samples data pointer was NULL.";
break;
case TOXAV_ERR_SEND_FRAME_FRIEND_NOT_FOUND:
code = OCTToxAVErrorSendFrameFriendNotFound;
failureReason = @"The friend number passed did not designate a valid friend.";
break;
case TOXAV_ERR_SEND_FRAME_FRIEND_NOT_IN_CALL:
code = OCTToxAVErrorSendFrameFriendNotInCall;
failureReason = @"This client is currently not in a call with the friend";
break;
case TOXAV_ERR_SEND_FRAME_SYNC:
code = OCTToxAVErrorSendFrameSync;
failureReason = @"Synchronization error occurred";
break;
case TOXAV_ERR_SEND_FRAME_INVALID:
code = OCTToxAVErrorSendFrameInvalid;
failureReason = @"One of the frame parameters was invalid. E.g. the resolution may be too small or too large, or the audio sampling rate may be unsupported";
break;
case TOXAV_ERR_SEND_FRAME_PAYLOAD_TYPE_DISABLED:
code = OCTToxAVErrorSendFramePayloadTypeDisabled;
failureReason = @"Either friend turned off audio/video receiving or we turned off sending for the said payload.";
break;
case TOXAV_ERR_SEND_FRAME_RTP_FAILED:
code = OCTToxAVErrorSendFrameRTPFailed;
failureReason = @"Failed to push frame through rtp interface";
break;
}
*error = [self createErrorWithCode:code description:description failureReason:failureReason];
return YES;
}
- (NSError *)createErrorWithCode:(NSUInteger)code
description:(NSString *)description
failureReason:(NSString *)failureReason
{
NSMutableDictionary *userInfo = [NSMutableDictionary new];
if (description) {
userInfo[NSLocalizedDescriptionKey] = description;
}
if (failureReason) {
userInfo[NSLocalizedFailureReasonErrorKey] = failureReason;
}
return [NSError errorWithDomain:kOCTToxAVErrorDomain code:code userInfo:userInfo];
}
- (void)updateTimerIntervalIfNeeded
{
uint64_t nextIterate = _toxav_iteration_interval(self.toxAV) * (NSEC_PER_SEC / 1000);
if (self.previousIterate == nextIterate) {
return;
}
self.previousIterate = nextIterate;
dispatch_source_set_timer(self.timer, dispatch_walltime(NULL, nextIterate), nextIterate, nextIterate / 5);
}
@end
#pragma mark - Callbacks
void callIncomingCallback(ToxAV *cToxAV,
uint32_t friendNumber,
bool audioEnabled,
bool videoEnabled,
void *userData)
{
OCTToxAV *toxAV = (__bridge OCTToxAV *)userData;
dispatch_async(dispatch_get_main_queue(), ^{
OCTLogCInfo(@"callIncomingCallback from friend %lu with audio:%d with video:%d", toxAV, (unsigned long)friendNumber, audioEnabled, videoEnabled);
if ([toxAV.delegate respondsToSelector:@selector(toxAV:receiveCallAudioEnabled:videoEnabled:friendNumber:)]) {
[toxAV.delegate toxAV:toxAV receiveCallAudioEnabled:audioEnabled videoEnabled:videoEnabled friendNumber:friendNumber];
}
});
}
void callStateCallback(ToxAV *cToxAV,
uint32_t friendNumber,
TOXAV_FRIEND_CALL_STATE cState,
void *userData)
{
OCTToxAV *toxAV = (__bridge OCTToxAV *)userData;
dispatch_async(dispatch_get_main_queue(), ^{
OCTLogCInfo(@"callStateCallback from friend %d with state: %d", toxAV, friendNumber, cState);
OCTToxAVCallState state = 0;
if (cState & TOXAV_FRIEND_CALL_STATE_ERROR) {
state |= OCTToxAVFriendCallStateError;
}
if (cState & TOXAV_FRIEND_CALL_STATE_FINISHED) {
state |= OCTToxAVFriendCallStateFinished;
}
if (cState & TOXAV_FRIEND_CALL_STATE_SENDING_A) {
state |= OCTToxAVFriendCallStateSendingAudio;
}
if (cState & TOXAV_FRIEND_CALL_STATE_SENDING_V) {
state |= OCTToxAVFriendCallStateSendingVideo;
}
if (cState & TOXAV_FRIEND_CALL_STATE_ACCEPTING_A) {
state |= OCTToxAVFriendCallStateAcceptingAudio;
}
if (cState & TOXAV_FRIEND_CALL_STATE_ACCEPTING_V) {
state |= OCTToxAVFriendCallStateAcceptingVideo;
}
if ([toxAV.delegate respondsToSelector:@selector(toxAV:callStateChanged:friendNumber:)]) {
[toxAV.delegate toxAV:toxAV callStateChanged:state friendNumber:friendNumber];
}
});
}
void audioBitRateStatusCallback(ToxAV *cToxAV,
uint32_t friendNumber,
uint32_t bit_rate,
void *userData)
{
OCTToxAV *toxAV = (__bridge OCTToxAV *)userData;
dispatch_async(dispatch_get_main_queue(), ^{
OCTLogCInfo(@"audioBitRateStatusCallback from friend %d bitRate: %d", toxAV, friendNumber, bit_rate);
if ([toxAV.delegate respondsToSelector:@selector(toxAV:audioBitRateStatus:forFriendNumber:)]) {
[toxAV.delegate toxAV:toxAV audioBitRateStatus:bit_rate forFriendNumber:friendNumber];
}
});
}
void videoBitRateStatusCallback(ToxAV *cToxAV,
uint32_t friendNumber,
uint32_t bit_rate,
void *userData)
{
OCTToxAV *toxAV = (__bridge OCTToxAV *)userData;
dispatch_async(dispatch_get_main_queue(), ^{
OCTLogCInfo(@"videoBitRateStatusCallback from friend %d bitRate: %d", toxAV, friendNumber, bit_rate);
if ([toxAV.delegate respondsToSelector:@selector(toxAV:videoBitRateStatus:forFriendNumber:)]) {
[toxAV.delegate toxAV:toxAV videoBitRateStatus:bit_rate forFriendNumber:friendNumber];
}
});
}
void receiveAudioFrameCallback(ToxAV *cToxAV,
uint32_t friendNumber,
OCTToxAVPCMData *pcm,
OCTToxAVSampleCount sampleCount,
OCTToxAVChannels channels,
OCTToxAVSampleRate sampleRate,
void *userData)
{
OCTToxAV *toxAV = (__bridge OCTToxAV *)userData;
// TOXAUDIO: -incoming-audio-
if ([toxAV.delegate respondsToSelector:@selector(toxAV:receiveAudio:sampleCount:channels:sampleRate:friendNumber:)]) {
[toxAV.delegate toxAV:toxAV receiveAudio:pcm sampleCount:sampleCount channels:channels sampleRate:sampleRate friendNumber:friendNumber];
}
}
void receiveVideoFrameCallback(ToxAV *cToxAV,
uint32_t friendNumber,
OCTToxAVVideoWidth width,
OCTToxAVVideoHeight height,
OCTToxAVPlaneData *yPlane, OCTToxAVPlaneData *uPlane, OCTToxAVPlaneData *vPlane,
OCTToxAVStrideData yStride, OCTToxAVStrideData uStride, OCTToxAVStrideData vStride,
void *userData)
{
OCTToxAV *toxAV = (__bridge OCTToxAV *)userData;
if ([toxAV.delegate respondsToSelector:@selector(toxAV:receiveVideoFrameWithWidth:height:yPlane:uPlane:vPlane:yStride:uStride:vStride:friendNumber:)]) {
[toxAV.delegate toxAV:toxAV
receiveVideoFrameWithWidth:width height:height
yPlane:yPlane uPlane:uPlane vPlane:vPlane
yStride:yStride uStride:uStride vStride:vStride
friendNumber:friendNumber];
}
}

View File

@ -0,0 +1,9 @@
// 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 "OCTToxAVConstants.h"
const OCTToxAVVideoBitRate kOCTToxAVVideoBitRateDisable = 0;
NSString *const kOCTToxAVErrorDomain = @"me.dvor.objcTox.ErrorDomain";

View File

@ -0,0 +1,25 @@
// 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 "OCTToxConstants.h"
#import <toxcore/tox.h>
const OCTToxFriendNumber kOCTToxFriendNumberFailure = UINT32_MAX;
const OCTToxFileNumber kOCTToxFileNumberFailure = UINT32_MAX;
const OCTToxFileSize kOCTToxFileSizeUnknown = UINT64_MAX;
NSString *const kOCTToxErrorDomain = @"me.dvor.objcTox.OCTToxErrorDomain";
const NSUInteger kOCTToxAddressLength = 2 * TOX_ADDRESS_SIZE;
const NSUInteger kOCTToxPublicKeyLength = 2 * TOX_PUBLIC_KEY_SIZE;
const NSUInteger kOCTToxSecretKeyLength = 2 * TOX_SECRET_KEY_SIZE;
const NSUInteger kOCTToxMaxNameLength = TOX_MAX_NAME_LENGTH;
const NSUInteger kOCTToxMaxStatusMessageLength = TOX_MAX_STATUS_MESSAGE_LENGTH;
const NSUInteger kOCTToxMaxFriendRequestLength = TOX_MAX_FRIEND_REQUEST_LENGTH;
const NSUInteger kOCTToxMaxMessageLength = TOX_MAX_MESSAGE_LENGTH;
const NSUInteger kOCTToxMaxCustomPacketSize = TOX_MAX_CUSTOM_PACKET_SIZE;
const NSUInteger kOCTToxMaxFileNameLength = TOX_MAX_FILENAME_LENGTH;
const NSUInteger kOCTToxHashLength = TOX_HASH_LENGTH;
const NSUInteger kOCTToxFileIdLength = TOX_FILE_ID_LENGTH;

View File

@ -0,0 +1,272 @@
// 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 "OCTToxEncryptSave.h"
#import <toxcore/toxencryptsave/toxencryptsave.h>
#import "OCTToxEncryptSaveConstants.h"
#import "OCTTox+Private.h"
@interface OCTToxEncryptSave ()
@property (assign, nonatomic) Tox_Pass_Key *passKey;
@end
@implementation OCTToxEncryptSave
#pragma mark - Lifecycle
- (nullable instancetype)initWithPassphrase:(nonnull NSString *)passphrase
toxData:(nullable NSData *)toxData
error:(NSError *__nullable *__nullable)error
{
self = [super init];
if (! self) {
return nil;
}
TOX_ERR_KEY_DERIVATION cError;
uint8_t salt[TOX_PASS_SALT_LENGTH];
bool hasSalt = false;
if (toxData) {
hasSalt = tox_get_salt(toxData.bytes, salt, NULL);
}
if (hasSalt) {
_passKey = tox_pass_key_derive_with_salt(
(const uint8_t *)[passphrase cStringUsingEncoding:NSUTF8StringEncoding],
[passphrase lengthOfBytesUsingEncoding:NSUTF8StringEncoding],
salt,
&cError);
}
else {
_passKey = tox_pass_key_derive(
(const uint8_t *)[passphrase cStringUsingEncoding:NSUTF8StringEncoding],
[passphrase lengthOfBytesUsingEncoding:NSUTF8StringEncoding],
&cError);
}
[OCTToxEncryptSave fillError:error withCErrorKeyDerivation:cError];
return (_passKey != NULL) ? self : nil;
}
- (void)dealloc
{
if (_passKey) {
tox_pass_key_free(_passKey);
}
}
#pragma mark - Public class methods
+ (BOOL)isDataEncrypted:(nonnull NSData *)data
{
return tox_is_data_encrypted(data.bytes);
}
+ (nullable NSData *)encryptData:(nonnull NSData *)data
withPassphrase:(nonnull NSString *)passphrase
error:(NSError *__nullable *__nullable)error
{
NSParameterAssert(data);
NSParameterAssert(passphrase);
return [OCTToxEncryptSave convertDataOfLength:data.length encrypt:YES withConvertBlock:^bool (uint8_t *out) {
TOX_ERR_ENCRYPTION cError;
bool result = tox_pass_encrypt(
data.bytes,
data.length,
(const uint8_t *)[passphrase cStringUsingEncoding:NSUTF8StringEncoding],
[passphrase lengthOfBytesUsingEncoding:NSUTF8StringEncoding],
out,
&cError);
[OCTToxEncryptSave fillError:error withCErrorEncryption:cError];
return result;
}];
}
+ (nullable NSData *)decryptData:(nonnull NSData *)data
withPassphrase:(nonnull NSString *)passphrase
error:(NSError *__nullable *__nullable)error
{
NSParameterAssert(data);
NSParameterAssert(passphrase);
return [OCTToxEncryptSave convertDataOfLength:data.length encrypt:NO withConvertBlock:^bool (uint8_t *out) {
TOX_ERR_DECRYPTION cError;
bool result = tox_pass_decrypt(
data.bytes,
data.length,
(const uint8_t *)[passphrase cStringUsingEncoding:NSUTF8StringEncoding],
[passphrase lengthOfBytesUsingEncoding:NSUTF8StringEncoding],
out,
&cError);
[OCTToxEncryptSave fillError:error withCErrorDecryption:cError];
return result;
}];
}
#pragma mark - Public instance method
- (nullable NSData *)encryptData:(nonnull NSData *)data error:(NSError *__nullable *__nullable)error
{
NSParameterAssert(data);
return [OCTToxEncryptSave convertDataOfLength:data.length encrypt:YES withConvertBlock:^bool (uint8_t *out) {
TOX_ERR_ENCRYPTION cError;
bool result = tox_pass_key_encrypt(
self.passKey,
data.bytes,
data.length,
out,
&cError);
[OCTToxEncryptSave fillError:error withCErrorEncryption:cError];
return result;
}];
}
- (nullable NSData *)decryptData:(nonnull NSData *)data error:(NSError *__nullable *__nullable)error
{
NSParameterAssert(data);
return [OCTToxEncryptSave convertDataOfLength:data.length encrypt:NO withConvertBlock:^bool (uint8_t *out) {
TOX_ERR_DECRYPTION cError;
bool result = tox_pass_key_decrypt(
self.passKey,
data.bytes,
data.length,
out,
&cError);
[OCTToxEncryptSave fillError:error withCErrorDecryption:cError];
return result;
}];
}
#pragma mark - Private
+ (NSData *)convertDataOfLength:(NSUInteger)dataLength
encrypt:(BOOL)encrypt
withConvertBlock:(bool (^)(uint8_t *out))convertBlock
{
NSUInteger outLength = dataLength + (encrypt ? TOX_PASS_ENCRYPTION_EXTRA_LENGTH : -TOX_PASS_ENCRYPTION_EXTRA_LENGTH);
uint8_t *out = malloc(outLength);
bool result = convertBlock(out);
NSData *resultData = nil;
if (result) {
resultData = [NSData dataWithBytes:out length:outLength];
}
if (out) {
free(out);
}
return resultData;
}
+ (BOOL)fillError:(NSError **)error withCErrorKeyDerivation:(TOX_ERR_KEY_DERIVATION)cError
{
if (! error || (cError == TOX_ERR_KEY_DERIVATION_OK)) {
return NO;
}
switch (cError) {
case TOX_ERR_KEY_DERIVATION_OK:
NSAssert(NO, @"We shouldn't be here");
return NO;
case TOX_ERR_KEY_DERIVATION_NULL:
case TOX_ERR_KEY_DERIVATION_FAILED:
*error = [OCTTox createErrorWithCode:OCTToxEncryptSaveKeyDerivationErrorFailed
description:@"Cannot create key from given passphrase"
failureReason:nil];
break;
}
return YES;
}
+ (BOOL)fillError:(NSError **)error withCErrorEncryption:(TOX_ERR_ENCRYPTION)cError
{
if (! error || (cError == TOX_ERR_ENCRYPTION_OK)) {
return NO;
}
OCTToxEncryptSaveEncryptionError code;
NSString *description = @"Encryption failed";
NSString *failureReason = nil;
switch (cError) {
case TOX_ERR_ENCRYPTION_OK:
NSAssert(NO, @"We shouldn't be here");
return NO;
case TOX_ERR_ENCRYPTION_NULL:
code = OCTToxEncryptSaveEncryptionErrorNull;
failureReason = @"Some input data was empty.";
break;
case TOX_ERR_ENCRYPTION_KEY_DERIVATION_FAILED:
case TOX_ERR_ENCRYPTION_FAILED:
code = OCTToxEncryptSaveEncryptionErrorFailed;
failureReason = @"Encryption failed, please report";
break;
}
*error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason];
return YES;
}
+ (BOOL)fillError:(NSError **)error withCErrorDecryption:(TOX_ERR_DECRYPTION)cError
{
if (! error || (cError == TOX_ERR_DECRYPTION_OK)) {
return NO;
}
OCTToxEncryptSaveDecryptionError code;
NSString *description = @"Decryption failed";
NSString *failureReason = nil;
switch (cError) {
case TOX_ERR_DECRYPTION_OK:
NSAssert(NO, @"We shouldn't be here");
return NO;
case TOX_ERR_DECRYPTION_NULL:
code = OCTToxEncryptSaveDecryptionErrorNull;
failureReason = @"Some input data was empty.";
break;
case TOX_ERR_DECRYPTION_BAD_FORMAT:
code = OCTToxEncryptSaveDecryptionErrorBadFormat;
failureReason = @"The input data has bad format";
break;
case TOX_ERR_DECRYPTION_INVALID_LENGTH:
case TOX_ERR_DECRYPTION_KEY_DERIVATION_FAILED:
case TOX_ERR_DECRYPTION_FAILED:
code = OCTToxEncryptSaveDecryptionErrorFailed;
failureReason = @"Decryption failed, passphrase is incorrect or data is corrupt";
break;
}
*error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason];
return YES;
}
@end

View File

@ -0,0 +1,11 @@
// 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 "OCTToxOptions.h"
@interface OCTToxOptions (Private)
@property (nonatomic, assign, readonly) struct Tox_Options *options;
@end

View File

@ -0,0 +1,212 @@
// 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 "OCTToxOptions+Private.h"
#import <toxcore/tox.h>
@interface OCTToxOptions ()
@property (nonatomic, assign, readonly) struct Tox_Options *options;
// Used to retain proxy_host option.
@property (nonatomic, copy) NSString *proxyHostStorage;
@end
@implementation OCTToxOptions
#pragma mark - Lifecycle
- (instancetype)init
{
self = [super init];
if (! self) {
return nil;
}
_options = tox_options_new(NULL);
return self;
}
- (void)dealloc
{
tox_options_free(_options);
}
#pragma mark - Description
- (NSString *)description
{
return [NSString stringWithFormat:@"OCTToxOptions:\n"
@"ipv6Enabled %d\n"
@"udpEnabled %d\n"
@"localDiscoveryEnabled %d\n"
@"proxyType %lu\n"
@"proxyHost %@\n"
@"proxyPort %d\n"
@"startPort %d\n"
@"endPort %d\n"
@"tcpPort %d\n"
@"holePunchingEnabled %d\n",
self.ipv6Enabled,
self.udpEnabled,
self.localDiscoveryEnabled,
self.proxyType,
self.proxyHost,
self.proxyPort,
self.startPort,
self.endPort,
self.tcpPort,
self.holePunchingEnabled];
}
#pragma mark - Properties
- (BOOL)ipv6Enabled
{
return tox_options_get_ipv6_enabled(self.options);
}
- (void)setIpv6Enabled:(BOOL)enabled
{
tox_options_set_ipv6_enabled(self.options, enabled);
}
- (BOOL)udpEnabled
{
return tox_options_get_udp_enabled(self.options);
}
- (void)setUdpEnabled:(BOOL)enabled
{
tox_options_set_udp_enabled(self.options, enabled);
}
- (BOOL)localDiscoveryEnabled
{
return tox_options_get_local_discovery_enabled(self.options);
}
- (void)setLocalDiscoveryEnabled:(BOOL)enabled
{
tox_options_set_local_discovery_enabled(self.options, enabled);
}
- (OCTToxProxyType)proxyType
{
switch (tox_options_get_proxy_type(self.options)) {
case TOX_PROXY_TYPE_NONE:
return OCTToxProxyTypeNone;
case TOX_PROXY_TYPE_HTTP:
return OCTToxProxyTypeHTTP;
case TOX_PROXY_TYPE_SOCKS5:
return OCTToxProxyTypeSocks5;
}
}
- (void)setProxyType:(OCTToxProxyType)type
{
switch (type) {
case OCTToxProxyTypeNone:
tox_options_set_proxy_type(self.options, TOX_PROXY_TYPE_NONE);
break;
case OCTToxProxyTypeHTTP:
tox_options_set_proxy_type(self.options, TOX_PROXY_TYPE_HTTP);
break;
case OCTToxProxyTypeSocks5:
tox_options_set_proxy_type(self.options, TOX_PROXY_TYPE_SOCKS5);
break;
}
}
- (NSString *)proxyHost
{
const char *cHost = tox_options_get_proxy_host(self.options);
if (cHost) {
return [NSString stringWithCString:cHost encoding:NSUTF8StringEncoding];
}
return nil;
}
- (void)setProxyHost:(NSString *)host
{
self.proxyHostStorage = host;
tox_options_set_proxy_host(self.options, self.proxyHostStorage.UTF8String);
}
- (uint16_t)proxyPort
{
return tox_options_get_proxy_port(self.options);
}
- (void)setProxyPort:(uint16_t)port
{
tox_options_set_proxy_port(self.options, port);
}
- (uint16_t)startPort
{
return tox_options_get_start_port(self.options);
}
- (void)setStartPort:(uint16_t)port
{
tox_options_set_start_port(self.options, port);
}
- (uint16_t)endPort
{
return tox_options_get_end_port(self.options);
}
- (void)setEndPort:(uint16_t)port
{
tox_options_set_end_port(self.options, port);
}
- (uint16_t)tcpPort
{
return tox_options_get_tcp_port(self.options);
}
- (void)setTcpPort:(uint16_t)port
{
tox_options_set_tcp_port(self.options, port);
}
- (BOOL)holePunchingEnabled
{
return tox_options_get_hole_punching_enabled(self.options);
}
- (void)setHolePunchingEnabled:(BOOL)enabled
{
tox_options_set_hole_punching_enabled(self.options, enabled);
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone
{
OCTToxOptions *options = [[[self class] allocWithZone:zone] init];
options.ipv6Enabled = self.ipv6Enabled;
options.udpEnabled = self.udpEnabled;
options.localDiscoveryEnabled = self.localDiscoveryEnabled;
options.proxyType = self.proxyType;
options.proxyHost = self.proxyHost;
options.proxyPort = self.proxyPort;
options.startPort = self.startPort;
options.endPort = self.endPort;
options.tcpPort = self.tcpPort;
options.holePunchingEnabled = self.holePunchingEnabled;
return options;
}
@end

View File

@ -0,0 +1,39 @@
// 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 "OCTFileStorageProtocol.h"
/**
* Default storage for files. It has following directory structure:
* /baseDirectory/saveFileName.tox - tox save file name. You can specify it in appropriate method.
* /baseDirectory/database - database with chats, messages and related stuff.
* /baseDirectory/database.encryptionkey - encryption key for database.
* /baseDirectory/files/ - downloaded and uploaded files will be stored here.
* /baseDirectory/avatars/ - avatars will be stored here.
* /temporaryDirectory/ - temporary files will be stored here.
*/
@interface OCTDefaultFileStorage : NSObject <OCTFileStorageProtocol>
/**
* Creates default file storage. Will use "save.tox" as default save file name.
*
* @param baseDirectory Base directory to use. It will have "files", "avatars" subdirectories.
* @param temporaryDirectory All temporary files will be stored here. You can pass NSTemporaryDirectory() here.
*/
- (instancetype)initWithBaseDirectory:(NSString *)baseDirectory temporaryDirectory:(NSString *)temporaryDirectory;
/**
* Creates default file storage.
*
* @param saveFileName Name of file to store tox save data. ".tox" extension will be appended to the name.
* @param baseDirectory Base directory to use. It will have "files", "avatars" subdirectories.
* @param temporaryDirectory All temporary files will be stored here. You can pass NSTemporaryDirectory() here.
*/
- (instancetype)initWithToxSaveFileName:(NSString *)saveFileName
baseDirectory:(NSString *)baseDirectory
temporaryDirectory:(NSString *)temporaryDirectory;
@end

View File

@ -0,0 +1,66 @@
// 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>
NS_ASSUME_NONNULL_BEGIN
@protocol OCTFileStorageProtocol <NSObject>
@required
/**
* Returns path where tox save data will be stored. Save file should have ".tox" extension.
* See Tox STS for more information: https://github.com/Tox/Tox-STS
*
* @return Full path to the file for loading/saving tox data.
*
* @warning Path should be file path. The file can be rewritten at any time while OCTManager is alive.
*/
@property (readonly) NSString *pathForToxSaveFile;
/**
* Returns file path for database to be stored in. Must be a file path, not directory.
* In database will be stored chats, messages and related stuff.
*
* @return Full path to the file for the database.
*
* @warning Path should be file path. The file can be rewritten at any time while OCTManager is alive.
*/
@property (readonly) NSString *pathForDatabase;
/**
* Returns file path for database encryption key to be stored in. Must be a file path, not a directory.
*
* @return Full path to the file to store database encryption key.
*
* @warning Path should be file path. The file can be rewritten at any time while OCTManager is alive.
*/
@property (readonly) NSString *pathForDatabaseEncryptionKey;
/**
* Returns path where all downloaded files will be stored.
*
* @return Full path to the directory with downloaded files.
*/
@property (readonly) NSString *pathForDownloadedFilesDirectory;
/**
* Returns path where all uploaded files will be stored.
*
* @return Full path to the directory with uploaded files.
*/
@property (readonly) NSString *pathForUploadedFilesDirectory;
/**
* Returns path where temporary files will be stored. This directory can be cleaned on relaunch of app.
* You can use NSTemporaryDirectory() here.
*
* @return Full path to the directory with temporary files.
*/
@property (readonly) NSString *pathForTemporaryFilesDirectory;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,60 @@
// 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 "OCTFileStorageProtocol.h"
#import "OCTToxOptions.h"
/**
* Configuration for OCTManager.
*/
@interface OCTManagerConfiguration : NSObject <NSCopying>
/**
* File storage to use.
*
* Default values: OCTDefaultFileStorage will be used with following parameters:
* - tox save file is stored at "{app document directory}/me.dvor.objcTox/save.tox"
* - database file is stored at "{app document directory}/me.dvor.objcTox/database"
* - database encryption key file is stored at "{app document directory}/me.dvor.objcTox/database.encryptionkey"
* - downloaded files are stored at "{app document directory}/me.dvor.objcTox/downloads"
* - uploaded files are stored at "{app document directory}/me.dvor.objcTox/uploads"
* - avatars are stored at "{app document directory}/me.dvor.objcTox/avatars"
* - temporary files are stored at NSTemporaryDirectory()
*/
@property (strong, nonatomic, nonnull) id<OCTFileStorageProtocol> fileStorage;
/**
* Options for tox to use.
*/
@property (strong, nonatomic, nonnull) OCTToxOptions *options;
/**
* If this parameter is set, tox save file will be copied from given path.
* You can set this property to import tox save from some other location.
*
* Default value: nil.
*/
@property (strong, nonatomic, nullable) NSString *importToxSaveFromPath;
/**
* When faux offline messaging is enabled, it is allowed to send message to
* offline friends. In that case message would be stored in database and resend
* when friend comes online.
*
* Default value: YES.
*/
@property (assign, nonatomic) BOOL useFauxOfflineMessaging;
/**
* This is default configuration for manager.
* Each property of OCTManagerConfiguration has "Default value" field. This method returns configuration
* with those default values set.
*
* @return Default configuration for OCTManager.
*/
+ (nonnull instancetype)defaultConfiguration;
@end

View File

@ -0,0 +1,95 @@
// 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"
NS_ASSUME_NONNULL_BEGIN
@class OCTManagerConfiguration;
@protocol OCTSubmanagerBootstrap;
@protocol OCTSubmanagerCalls;
@protocol OCTSubmanagerChats;
@protocol OCTSubmanagerFiles;
@protocol OCTSubmanagerFriends;
@protocol OCTSubmanagerObjects;
@protocol OCTSubmanagerUser;
@protocol OCTManager <NSObject>
/**
* Submanager responsible for connecting to other nodes.
*/
@property (strong, nonatomic, readonly) id<OCTSubmanagerBootstrap> bootstrap;
/**
* Submanager with all video/calling methods.
*/
@property (strong, nonatomic, readonly) id<OCTSubmanagerCalls> calls;
/**
* Submanager with all chats methods.
*/
@property (strong, nonatomic, readonly) id<OCTSubmanagerChats> chats;
/**
* Submanager with all files methods.
*/
@property (strong, nonatomic, readonly) id<OCTSubmanagerFiles> files;
/**
* Submanager with all friends methods.
*/
@property (strong, nonatomic, readonly) id<OCTSubmanagerFriends> friends;
/**
* Submanager with all objects methods.
*/
@property (strong, nonatomic, readonly) id<OCTSubmanagerObjects> objects;
/**
* Submanager with all user methods.
*/
@property (strong, nonatomic, readonly) id<OCTSubmanagerUser> user;
/**
* Configuration used by OCTManager.
*
* @return Copy of configuration used by manager.
*/
- (OCTManagerConfiguration *)configuration;
/**
* Copies tox save file to temporary directory and return path to it.
*
* @param error NSFileManager error in case if file cannot be copied.
*
* @return Temporary path of current tox save file.
*/
- (nullable NSString *)exportToxSaveFileAndReturnError:(NSError *__nullable *__nullable)error;
/**
* Set password to encrypt tox save file and database.
*
* @param newPassword New password used to encrypt tox save file and database.
* @param oldPassword Old password.
*
* @return YES on success, NO on failure (if old password doesn't match).
*/
- (BOOL)changeEncryptPassword:(nonnull NSString *)newPassword oldPassword:(nonnull NSString *)oldPassword;
/**
* Checks if manager is encrypted with given password.
*
* @param password Password to verify.
*
* @return YES if manager is encrypted with given password, NO otherwise.
*/
- (BOOL)isManagerEncryptedWithPassword:(nonnull NSString *)password;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,326 @@
// 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 "OCTToxConstants.h"
/**
* Maximum avatar size as defined in
* https://tox.gitbooks.io/tox-client-standard/content/user_identification/avatar.html
*/
static const OCTToxFileSize kOCTManagerMaxAvatarSize = 65536;
typedef NS_ENUM(NSInteger, OCTFetchRequestType) {
OCTFetchRequestTypeFriend,
OCTFetchRequestTypeFriendRequest,
OCTFetchRequestTypeChat,
OCTFetchRequestTypeCall,
OCTFetchRequestTypeMessageAbstract,
};
typedef NS_ENUM(NSInteger, OCTMessageFileType) {
/**
* File is incoming and is waiting confirmation of user to be downloaded.
* Please start loading or cancel it with <<placeholder>> method.
*/
OCTMessageFileTypeWaitingConfirmation,
/**
* File is downloading or uploading.
*/
OCTMessageFileTypeLoading,
/**
* Downloading or uploading of file is paused.
*/
OCTMessageFileTypePaused,
/**
* Downloading or uploading of file was canceled.
*/
OCTMessageFileTypeCanceled,
/**
* File is fully loaded.
* In case of incoming file now it can be shown to user.
*/
OCTMessageFileTypeReady,
};
typedef NS_ENUM(NSInteger, OCTMessageFilePausedBy) {
/**
* File transfer isn't paused.
*/
OCTMessageFilePausedByNone = 0,
/**
* File transfer is paused by user.
*/
OCTMessageFilePausedByUser = 1 << 0,
/**
* File transfer is paused by friend.
*/
OCTMessageFilePausedByFriend = 1 << 1,
};
typedef NS_ENUM(NSInteger, OCTMessageCallEvent) {
/**
* Call was answered.
*/
OCTMessageCallEventAnswered,
/**
* Call was unanswered.
*/
OCTMessageCallEventUnanswered,
};
typedef NS_ENUM(NSInteger, OCTCallStatus) {
/**
* Call is currently ringing.
*/
OCTCallStatusRinging,
/**
* Call is currently dialing a chat.
*/
OCTCallStatusDialing,
/**
* Call is currently active in session.
*/
OCTCallStatusActive,
};
typedef NS_OPTIONS(NSInteger, OCTCallPausedStatus) {
/**
* Call is not paused
*/
OCTCallPausedStatusNone = 0,
/**
* Call is paused by the user
*/
OCTCallPausedStatusByUser = 1 << 0,
/**
* Call is paused by friend
*/
OCTCallPausedStatusByFriend = 1 << 1,
};
extern NSString *const kOCTManagerErrorDomain;
typedef NS_ENUM(NSInteger, OCTManagerInitError) {
/**
* Cannot create symmetric key from given passphrase.
*/
OCTManagerInitErrorPassphraseFailed,
/** ---------------------------------------- */
/**
* Cannot copy tox save at `importToxSaveFromPath` path.
*/
OCTManagerInitErrorCannotImportToxSave,
/** ---------------------------------------- */
/**
* Cannot create encryption key.
*/
OCTManagerInitErrorDatabaseKeyCannotCreateKey,
/**
* Cannot read encryption key.
*/
OCTManagerInitErrorDatabaseKeyCannotReadKey,
/**
* Old unencrypted database was found and migration attempt was made. However migration failed for some reason.
*
* You can check NSLocalizedDescriptionKey and NSLocalizedFailureReasonErrorKey for more info.
*/
OCTManagerInitErrorDatabaseKeyMigrationToEncryptedFailed,
/**
* Cannot decrypt database key file.
* Some input data was empty.
*/
OCTManagerInitErrorDatabaseKeyDecryptNull,
/**
* Cannot decrypt database key file.
* The input data is missing the magic number (i.e. wasn't created by this module, or is corrupted).
*/
OCTManagerInitErrorDatabaseKeyDecryptBadFormat,
/**
* Cannot decrypt database key file.
* The encrypted byte array could not be decrypted. Either the data was corrupt or the password/key was incorrect.
*/
OCTManagerInitErrorDatabaseKeyDecryptFailed,
/** ---------------------------------------- */
/**
* Cannot decrypt tox save file.
* Some input data was empty.
*/
OCTManagerInitErrorToxFileDecryptNull,
/**
* Cannot decrypt tox save file.
* The input data is missing the magic number (i.e. wasn't created by this module, or is corrupted).
*/
OCTManagerInitErrorToxFileDecryptBadFormat,
/**
* Cannot decrypt tox save file.
* The encrypted byte array could not be decrypted. Either the data was corrupt or the password/key was incorrect.
*/
OCTManagerInitErrorToxFileDecryptFailed,
/** ---------------------------------------- */
/**
* Cannot create tox.
* Unknown error occurred.
*/
OCTManagerInitErrorCreateToxUnknown,
/**
* Cannot create tox.
* Was unable to allocate enough memory to store the internal structures for the Tox object.
*/
OCTManagerInitErrorCreateToxMemoryError,
/**
* Cannot create tox.
* Was unable to bind to a port. This may mean that all ports have already been bound,
* e.g. by other Tox instances, or it may mean a permission error.
*/
OCTManagerInitErrorCreateToxPortAlloc,
/**
* Cannot create tox.
* proxyType was invalid.
*/
OCTManagerInitErrorCreateToxProxyBadType,
/**
* Cannot create tox.
* proxyAddress had an invalid format or was nil (while proxyType was set).
*/
OCTManagerInitErrorCreateToxProxyBadHost,
/**
* Cannot create tox.
* proxyPort was invalid.
*/
OCTManagerInitErrorCreateToxProxyBadPort,
/**
* Cannot create tox.
* The proxy host passed could not be resolved.
*/
OCTManagerInitErrorCreateToxProxyNotFound,
/**
* Cannot create tox.
* The saved data to be loaded contained an encrypted save.
*/
OCTManagerInitErrorCreateToxEncrypted,
/**
* Cannot create tox.
* The data format was invalid. This can happen when loading data that was
* saved by an older version of Tox, or when the data has been corrupted.
* When loading from badly formatted data, some data may have been loaded,
* and the rest is discarded. Passing an invalid length parameter also
* causes this error.
*/
OCTManagerInitErrorCreateToxBadFormat,
};
typedef NS_ENUM(NSInteger, OCTSetUserAvatarError) {
/**
* User avatar size is too big. It should be <= kOCTManagerMaxAvatarSize.
*/
OCTSetUserAvatarErrorTooBig,
};
typedef NS_ENUM(NSInteger, OCTSendFileError) {
/**
* Internal error occured while sending file.
* Check logs for more info.
*/
OCTSendFileErrorInternalError,
/**
* Cannot read file.
*/
OCTSendFileErrorCannotReadFile,
/**
* Cannot save send file to uploads folder.
*/
OCTSendFileErrorCannotSaveFileToUploads,
/**
* Friend to send file to was not found.
*/
OCTSendFileErrorFriendNotFound,
/**
* Friend is not connected at the moment.
*/
OCTSendFileErrorFriendNotConnected,
/**
* Filename length exceeded kOCTToxMaxFileNameLength bytes.
*/
OCTSendFileErrorNameTooLong,
/**
* Too many ongoing transfers. The maximum number of concurrent file transfers
* is 256 per friend per direction (sending and receiving).
*/
OCTSendFileErrorTooMany,
};
typedef NS_ENUM(NSInteger, OCTAcceptFileError) {
/**
* Internal error occured while sending file.
* Check logs for more info.
*/
OCTAcceptFileErrorInternalError,
/**
* File is not available for writing.
*/
OCTAcceptFileErrorCannotWriteToFile,
/**
* Friend to send file to was not found.
*/
OCTAcceptFileErrorFriendNotFound,
/**
* Friend is not connected at the moment.
*/
OCTAcceptFileErrorFriendNotConnected,
/**
* Wrong message specified (with no friend, no file or not waiting for confirmation).
*/
OCTAcceptFileErrorWrongMessage,
};
typedef NS_ENUM(NSInteger, OCTFileTransferError) {
/**
* Wrong message specified (with no file).
*/
OCTFileTransferErrorWrongMessage,
};

View File

@ -0,0 +1,35 @@
// 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>
NS_ASSUME_NONNULL_BEGIN
@class OCTManagerConfiguration;
@protocol OCTManager;
@interface OCTManagerFactory : NSObject
/**
* Create manager with configuration. There is no way to change configuration after init method. If you'd like to
* change it you have to recreate OCTManager.
*
* @param configuration Configuration to be used.
* @param encryptPassword Password used to encrypt/decrypt tox save file and database.
* Tox file will be encrypted automatically if it wasn't encrypted before.
* @param successBlock Block called on success with initialized OCTManager. Will be called on main thread.
* @param failureBlock Block called on failure. Will be called on main thread.
* @param error If an error occurs, this pointer is set to an actual error object containing the error information.
* See OCTManagerInitError for all error codes.
*
* @warning This method should be called on main thread.
*/
+ (void)managerWithConfiguration:(OCTManagerConfiguration *)configuration
encryptPassword:(NSString *)encryptPassword
successBlock:(nullable void (^)(id<OCTManager> manager))successBlock
failureBlock:(nullable void (^)(NSError *error))failureBlock;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,84 @@
// 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 "OCTChat.h"
#import "OCTFriend.h"
#import "OCTManagerConstants.h"
/**
* Please note that all properties of this object are readonly.
* All management of calls are handeled through OCTCallSubmanagerCalls.
*/
@interface OCTCall : OCTObject
/**
* OCTChat related session with the call.
**/
@property (nonnull) OCTChat *chat;
/**
* Call status
**/
@property OCTCallStatus status;
/**
* This property contains paused status for Active call.
*/
@property OCTCallPausedStatus pausedStatus;
/**
* The friend who started the call.
* Nil if the you started the call yourself.
**/
@property (nullable) OCTFriend *caller;
/**
* Video device is active for this call
*/
@property BOOL videoIsEnabled;
/**
* Friend is sending audio.
*/
@property BOOL friendSendingAudio;
/**
* Friend is sending video.
*/
@property BOOL friendSendingVideo;
/**
* Friend is accepting audio.
*/
@property BOOL friendAcceptingAudio;
/**
* Friend is accepting video.
*/
@property BOOL friendAcceptingVideo;
/**
* Call duration
**/
@property NSTimeInterval callDuration;
/**
* The on hold start interval when call was put on hold.
*/
@property NSTimeInterval onHoldStartInterval;
/**
* The date when the call was put on hold.
*/
- (nullable NSDate *)onHoldDate;
/**
* Indicates if call is outgoing or incoming.
* In case if it is incoming you can check `caller` property for friend.
**/
- (BOOL)isOutgoing;
@end

View File

@ -0,0 +1,74 @@
// 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 "OCTObject.h"
#import "OCTFriend.h"
@class OCTMessageAbstract;
/**
* Please note that all properties of this object are readonly.
* You can change some of them only with appropriate method in OCTSubmanagerObjects.
*/
@interface OCTChat : OCTObject
/**
* Array with OCTFriends that participate in this chat.
*/
@property (nonnull) RLMArray<OCTFriend> *friends;
/**
* The latest message that was send or received.
*/
@property (nullable) OCTMessageAbstract *lastMessage;
/**
* This property can be used for storing entered text that wasn't send yet.
*
* To change please use OCTSubmanagerObjects method.
*
* May be empty.
*/
@property (nullable) NSString *enteredText;
/**
* This property stores last date interval when chat was read.
* `hasUnreadMessages` method use lastReadDateInterval to determine if there are unread messages.
*
* To change please use OCTSubmanagerObjects method.
*/
@property NSTimeInterval lastReadDateInterval;
/**
* Date interval of lastMessage or chat creationDate if there is no last message.
*
* This property is workaround to support sorting. Should be replaced with keypath
* lastMessage.dateInterval sorting in future.
* See https://github.com/realm/realm-cocoa/issues/1277
*/
@property NSTimeInterval lastActivityDateInterval;
/**
* The date when chat was read last time.
*/
- (nullable NSDate *)lastReadDate;
/**
* Returns date of lastMessage or chat creationDate if there is no last message.
*/
- (nullable NSDate *)lastActivityDate;
/**
* If there are unread messages in chat YES is returned. All messages that have date later than lastReadDateInterval
* are considered as unread.
*
* Please note that you have to set lastReadDateInterval to make this method work.
*
* @return YES if there are unread messages, NO otherwise.
*/
- (BOOL)hasUnreadMessages;
@end
RLM_ARRAY_TYPE(OCTChat)

View File

@ -0,0 +1,110 @@
// 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 "OCTObject.h"
#import "OCTToxConstants.h"
/**
* Class that represents friend (or just simply contact).
*
* Please note that all properties of this object are readonly.
* You can change some of them only with appropriate method in OCTSubmanagerObjects.
*/
@interface OCTFriend : OCTObject
/**
* Friend number that is unique for Tox.
* In case if friend will be deleted, old id may be reused on new friend creation.
*/
@property OCTToxFriendNumber friendNumber;
/**
* Nickname of friend.
*
* When friend is created it is set to the publicKey.
* It is set to name when obtaining name for the first time.
* After that name is unchanged (unless it is changed explicitly).
*
* To change please use OCTSubmanagerObjects method.
*/
@property (nonnull) NSString *nickname;
/**
* Public key of a friend, is kOCTToxPublicKeyLength length.
* Is constant, cannot be changed.
*/
@property (nonnull) NSString *publicKey;
/**
* Name of a friend.
*
* May be empty.
*/
@property (nullable) NSString *name;
/**
* Status message of a friend.
*
* May be empty.
*/
@property (nullable) NSString *statusMessage;
/**
* Status message of a friend.
*/
@property OCTToxUserStatus status;
/**
* Property specifies if friend is connected. For type of connection you can check
* connectionStatus property.
*/
@property BOOL isConnected;
/**
* Connection status message of a friend.
*/
@property OCTToxConnectionStatus connectionStatus;
/**
* The date interval when friend was last seen online.
* Contains actual information in case if friend has connectionStatus offline.
*/
@property NSTimeInterval lastSeenOnlineInterval;
/**
* Whether friend is typing now in current chat.
*/
@property BOOL isTyping;
/**
* Data representation of friend's avatar.
*/
@property (nullable) NSData *avatarData;
/**
* The date when friend was last seen online.
* Contains actual information in case if friend has connectionStatus offline.
*/
- (nullable NSDate *)lastSeenOnline;
/**
* Push Token of a friend.
*
* May be empty.
*/
@property (nullable) NSString *pushToken;
/**
* Indicate if a friend has msgV3 Capability.
*/
@property BOOL msgv3Capability;
/**
* Friend's capabilities. A 64 bit unsigned integer.
*/
@property (nonnull) NSString *capabilities2;
@end
RLM_ARRAY_TYPE(OCTFriend)

View File

@ -0,0 +1,35 @@
// 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 "OCTObject.h"
/**
* Please note that all properties of this object are readonly.
* You can change some of them only with appropriate method in OCTSubmanagerObjects.
*/
@interface OCTFriendRequest : OCTObject
/**
* Public key of a friend.
*/
@property (nonnull) NSString *publicKey;
/**
* Message that friend did send with friend request.
*/
@property (nullable) NSString *message;
/**
* Date interval when friend request was received (since 1970).
*/
@property NSTimeInterval dateInterval;
/**
* Date when friend request was received.
*/
- (nonnull NSDate *)date;
@end
RLM_ARRAY_TYPE(OCTFriendRequest)

View File

@ -0,0 +1,67 @@
// 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 "OCTObject.h"
@class OCTFriend;
@class OCTChat;
@class OCTMessageText;
@class OCTMessageFile;
@class OCTMessageCall;
/**
* An abstract message that represents one chunk of chat history.
*
* Please note that all properties of this object are readonly.
* You can change some of them only with appropriate method in OCTSubmanagerObjects.
*/
@interface OCTMessageAbstract : OCTObject
/**
* The date interval when message was send/received.
*/
@property NSTimeInterval dateInterval;
/**
* Unixtimestamp when messageV3 was sent or 0.
*/
@property NSTimeInterval tssent;
/**
* Unixtimestamp when messageV3 was received or 0.
*/
@property NSTimeInterval tsrcvd;
/**
* Unique identifier of friend that have send message.
* If the message if outgoing senderUniqueIdentifier is nil.
*/
@property (nullable) NSString *senderUniqueIdentifier;
/**
* The chat message message belongs to.
*/
@property (nonnull) NSString *chatUniqueIdentifier;
/**
* Message has one of the following properties.
*/
@property (nullable) OCTMessageText *messageText;
@property (nullable) OCTMessageFile *messageFile;
@property (nullable) OCTMessageCall *messageCall;
/**
* The date when message was send/received.
*/
- (nonnull NSDate *)date;
/**
* Indicates if message is outgoing or incoming.
* In case if it is incoming you can check `sender` property for message sender.
*/
- (BOOL)isOutgoing;
@end
RLM_ARRAY_TYPE(OCTMessageAbstract)

View File

@ -0,0 +1,20 @@
// 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 "OCTObject.h"
#import "OCTManagerConstants.h"
@interface OCTMessageCall : OCTObject
/**
* The length of the call in seconds.
**/
@property NSTimeInterval callDuration;
/**
* The type of message call.
**/
@property OCTMessageCallEvent callEvent;
@end

View File

@ -0,0 +1,61 @@
// 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 "OCTObject.h"
#import "OCTToxConstants.h"
#import "OCTManagerConstants.h"
/**
* Message that contains file, that has been send/received. Represents pending, canceled and loaded files.
*
* Please note that all properties of this object are readonly.
* You can change some of them only with appropriate method in OCTSubmanagerObjects.
*/
@interface OCTMessageFile : OCTObject
/**
* The current state of file.
*/
@property OCTMessageFileType fileType;
/**
* In case if fileType is equal to OCTMessageFileTypePaused this property will contain information
* by whom file transfer was paused.
*/
@property OCTMessageFilePausedBy pausedBy;
/**
* Size of file in bytes.
*/
@property OCTToxFileSize fileSize;
/**
* Name of the file as specified by sender. Note that actual fileName in path
* may differ from this fileName.
*/
@property (nullable) NSString *fileName;
/**
* Uniform Type Identifier of file.
*/
@property (nullable) NSString *fileUTI;
/**
* Path of file on disk. If you need fileName to show to user please use
* `fileName` property. filePath has it's own random fileName.
*
* In case of incoming file filePath will have value only if fileType is OCTMessageFileTypeReady
*/
- (nullable NSString *)filePath;
// Properties and methods below are for internal use.
// Do not use them or rely on them. They may change in any moment.
@property int internalFileNumber;
@property (nullable) NSString *internalFilePath;
- (void)internalSetFilePath:(nullable NSString *)path;
@end
RLM_ARRAY_TYPE(OCTMessageFile)

View File

@ -0,0 +1,49 @@
// 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 "OCTObject.h"
#import "OCTToxConstants.h"
@class OCTToxConstants;
/**
* Simple text message.
*
* Please note that all properties of this object are readonly.
* You can change some of them only with appropriate method in OCTSubmanagerObjects.
*/
@interface OCTMessageText : OCTObject
/**
* The text of the message.
*/
@property (nullable) NSString *text;
/**
* Indicate if message is delivered. Actual only for outgoing messages.
*/
@property BOOL isDelivered;
/**
* Type of the message.
*/
@property OCTToxMessageType type;
@property OCTToxMessageId messageId;
/**
* msgV3 Hash as uppercase Hexstring.
*
* May be empty if message is not v3.
*/
@property (nullable) NSString *msgv3HashHex;
/**
* Indicate if message has triggered a push notification.
*/
@property BOOL sentPush;
@end
RLM_ARRAY_TYPE(OCTMessageText)

View File

@ -0,0 +1,33 @@
// 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>
/**
* Please note that all properties of this object are readonly.
* You can change some of them only with appropriate method in OCTSubmanagerObjects.
*/
@interface OCTObject : RLMObject
/**
* The unique identifier of object.
*/
@property NSString *uniqueIdentifier;
/**
* Returns a string that represents the contents of the receiving class.
*/
- (NSString *)description;
/**
* Returns a Boolean value that indicates whether the receiver and a given object are equal.
*/
- (BOOL)isEqual:(id)object;
/**
* Returns an integer that can be used as a table address in a hash table structure.
*/
- (NSUInteger)hash;
@end

Some files were not shown because too many files have changed in this diff Show More