Initial commit
This commit is contained in:
		
							
								
								
									
										31
									
								
								local_pod_repo/objcTox/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								local_pod_repo/objcTox/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| # Xcode | ||||
| build/* | ||||
| *.pbxuser | ||||
| !default.pbxuser | ||||
| *.mode1v3 | ||||
| !default.mode1v3 | ||||
| *.mode2v3 | ||||
| !default.mode2v3 | ||||
| *.perspectivev3 | ||||
| !default.perspectivev3 | ||||
| *.xcworkspace | ||||
| !default.xcworkspace | ||||
| xcuserdata | ||||
| profile | ||||
| *.moved-aside | ||||
| *.mode1 | ||||
| *.perspective | ||||
| xcshareddata | ||||
|  | ||||
| # Exclude temp nibs and swap files | ||||
| *~.nib | ||||
| *.swp | ||||
|  | ||||
| # Exclude OS X folder attributes | ||||
| .DS_Store | ||||
| # | ||||
| # Exclude AppCode files | ||||
| .idea | ||||
|  | ||||
| Pods | ||||
| Podfile.lock | ||||
| @@ -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) | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user