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