1
0
mirror of https://github.com/danog/Telegram.git synced 2024-12-11 17:09:46 +01:00
Telegram/Watch/Bridge/TGBridgeClient.m
2016-02-25 01:03:51 +01:00

645 lines
18 KiB
Objective-C

#import "TGBridgeClient.h"
#import "TGBridgeCommon.h"
#import "TGBridgeChat.h"
#import <WatchConnectivity/WatchConnectivity.h>
#import "TGBridgeSubscription.h"
#import "TGBridgeResponse.h"
#import "TGBridgeContext.h"
#import "TGFileCache.h"
#import "TGBridgeChatMessageListSubscription.h"
#import "TGBridgeStickersSignals.h"
#import "TGBridgePresetsSignals.h"
#import "TGExtensionDelegate.h"
#import <libkern/OSAtomic.h>
NSString *const TGBridgeContextDomain = @"com.telegram.BridgeContext";
const NSTimeInterval TGBridgeClientTimerInterval = 4.0;
const NSTimeInterval TGBridgeClientWakeInterval = 2.0;
@interface TGBridgeClient () <WCSessionDelegate>
{
int32_t _sessionId;
bool _reachable;
bool _processingNotification;
SMulticastSignalManager *_signalManager;
SMulticastSignalManager *_fileSignalManager;
SPipe *_contextPipe;
SPipe *_actualReachabilityPipe;
SPipe *_reachabilityPipe;
SPipe *_userInfoPipe;
dispatch_queue_t _contextQueue;
OSSpinLock _outgoingQueueLock;
NSMutableArray *_outgoingMessageQueue;
NSArray *_stickerPacks;
OSSpinLock _stickerPacksLock;
NSMutableDictionary *_subscriptions;
NSTimeInterval _lastForegroundEntry;
STimer *_timer;
bool _sentFirstPing;
bool _isActive;
}
@property (nonatomic, readonly) WCSession *session;
@end
@implementation TGBridgeClient
- (instancetype)init
{
self = [super init];
if (self != nil)
{
int32_t sessionId = 0;
arc4random_buf(&sessionId, sizeof(int32_t));
_sessionId = sessionId;
_contextQueue = dispatch_queue_create(TGBridgeContextDomain.UTF8String, nil);
_signalManager = [[SMulticastSignalManager alloc] init];
_fileSignalManager = [[SMulticastSignalManager alloc] init];
_contextPipe = [[SPipe alloc] init];
_userInfoPipe = [[SPipe alloc] init];
_actualReachabilityPipe = [[SPipe alloc] init];
_reachabilityPipe = [[SPipe alloc] init];
_reachable = true;
_outgoingMessageQueue = [[NSMutableArray alloc] init];
_subscriptions = [[NSMutableDictionary alloc] init];
self.session.delegate = self;
[self.session activateSession];
TGLog(@"BridgeClient: initialized");
[self ping];
}
return self;
}
- (void)transferUserInfo:(NSDictionary *)userInfo
{
[self.session transferUserInfo:userInfo];
}
- (SSignal *)requestSignalWithSubscription:(TGBridgeSubscription *)subscription
{
if ([subscription isKindOfClass:[TGBridgeChatMessageSubscription class]])
_processingNotification = true;
else if (!_sentFirstPing)
[self ping];
NSData *messageData = [NSKeyedArchiver archivedDataWithRootObject:subscription];
void (^transcribe)(id, SSubscriber *, bool *) = ^(id message, SSubscriber *subscriber, bool *completed)
{
TGBridgeResponse *response = nil;
if ([message isKindOfClass:[TGBridgeResponse class]])
{
response = message;
}
else if ([message isKindOfClass:[NSData class]])
{
@try
{
id unarchivedMessage = [NSKeyedUnarchiver unarchiveObjectWithData:message];
if ([unarchivedMessage isKindOfClass:[TGBridgeResponse class]])
response = (TGBridgeResponse *)unarchivedMessage;
}
@catch (NSException *exception)
{
}
}
if (response == nil)
return;
switch (response.type)
{
case TGBridgeResponseTypeNext:
[subscriber putNext:response.next];
break;
case TGBridgeResponseTypeFailed:
[subscriber putError:response.error];
break;
case TGBridgeResponseTypeCompleted:
if (completed != NULL)
*completed = true;
[subscriber putCompletion];
break;
default:
break;
}
};
__weak TGBridgeClient *weakSelf = self;
return [[[[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
{
SDisposableSet *combinedDisposable = [[SDisposableSet alloc] init];
SMetaDisposable *currentDisposable = [[SMetaDisposable alloc] init];
__block bool completed = false;
[combinedDisposable add:currentDisposable];
void (^afterSendMessage)(void) = ^
{
__strong TGBridgeClient *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[combinedDisposable add:[[strongSelf->_signalManager multicastedPipeForKey:[NSString stringWithFormat:@"%lld", subscription.identifier]] startWithNext:^(id next)
{
transcribe(next, subscriber, NULL);
} error:^(id error)
{
[subscriber putError:error];
} completed:^
{
[subscriber putCompletion];
}]];
};
[currentDisposable setDisposable:[[[self sendMessageData:messageData] onStart:^
{
__strong TGBridgeClient *strongSelf = weakSelf;
if (strongSelf != nil)
strongSelf->_subscriptions[@(subscription.identifier)] = subscription;
}] startWithNext:^(id next)
{
__strong TGBridgeClient *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if ([subscription isKindOfClass:[TGBridgeChatMessageSubscription class]])
strongSelf->_processingNotification = false;
transcribe(next, subscriber, &completed);
} error:^(NSError *error)
{
if ([error isKindOfClass:[NSError class]] && error.domain == WCErrorDomain)
{
__strong TGBridgeClient *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf _enqueueMessage:messageData];
afterSendMessage();
}
else
{
[subscriber putError:error];
}
} completed:^
{
if (completed)
return;
afterSendMessage();
}]];
return combinedDisposable;
}] onCompletion:^
{
__strong TGBridgeClient *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf->_subscriptions removeObjectForKey:@(subscription.identifier)];
}] onDispose:^
{
__strong TGBridgeClient *strongSelf = weakSelf;
if (strongSelf != nil)
{
[strongSelf->_subscriptions removeObjectForKey:@(subscription.identifier)];
[strongSelf unsubscribe:subscription.identifier];
}
}];
}
- (void)unsubscribe:(int64_t)identifier
{
TGBridgeDisposal *disposal = [[TGBridgeDisposal alloc] initWithIdentifier:identifier];
NSData *message = [NSKeyedArchiver archivedDataWithRootObject:disposal];
[self.session sendMessageData:message replyHandler:nil errorHandler:^(NSError *error)
{
[self _logError:error];
}];
}
- (SSignal *)sendMessageData:(NSData *)messageData
{
return [[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
{
[self.session sendMessageData:messageData replyHandler:^(NSData *replyMessageData)
{
if (replyMessageData.length > 0)
[subscriber putNext:replyMessageData];
[subscriber putCompletion];
} errorHandler:^(NSError * _Nonnull error)
{
[self _logError:error];
[subscriber putError:error];
}];
return nil;
}];
}
- (void)sendRawMessageData:(NSData *)messageData replyHandler:(void (^)(NSData *))replyHandler errorHandler:(void (^)(NSError *))errorHandler
{
[self.session sendMessageData:messageData replyHandler:replyHandler errorHandler:errorHandler];
}
#pragma mark -
- (SSignal *)contextSignal
{
if (self.session.receivedApplicationContext.allKeys.count > 0)
{
SSignal *initialSignal = [SSignal single:[[TGBridgeContext alloc] initWithDictionary:self.session.receivedApplicationContext]];
return [initialSignal then:_contextPipe.signalProducer()];
}
else
{
return _contextPipe.signalProducer();
}
}
#pragma mark -
- (SSignal *)fileSignalForKey:(NSString *)key
{
return [_fileSignalManager multicastedPipeForKey:key];
}
- (void)sendFileWithURL:(NSURL *)url metadata:(NSDictionary *)metadata
{
[self.session transferFile:url metadata:metadata];
}
#pragma mark -
- (NSArray *)stickerPacks
{
OSSpinLockLock(&_stickerPacksLock);
if (_stickerPacks != nil)
{
NSArray *stickerPacks = [_stickerPacks copy];
OSSpinLockUnlock(&_stickerPacksLock);
return stickerPacks;
}
else
{
NSArray *stickerPacks = [self readStickerPacks];
if (stickerPacks == nil)
stickerPacks = [NSArray array];
_stickerPacks = stickerPacks;
OSSpinLockUnlock(&_stickerPacksLock);
return stickerPacks;
}
}
- (NSArray *)readStickerPacks
{
NSURL *url = [TGBridgeStickersSignals stickerPacksURL];
NSData *data = [[NSData alloc] initWithContentsOfURL:url];
if (data == nil)
return nil;
NSArray *stickerPacks = nil;
@try
{
stickerPacks = [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
@catch (NSException *exception)
{
}
if (![stickerPacks isKindOfClass:[NSArray class]])
return nil;
return stickerPacks;
}
#pragma mark -
- (void)session:(WCSession *)session didReceiveMessageData:(NSData *)messageData
{
[self handleReceivedData:messageData replyHandler:nil];
}
- (void)session:(WCSession *)session didReceiveMessageData:(NSData *)messageData replyHandler:(nonnull void (^)(NSData * _Nonnull))replyHandler
{
[self handleReceivedData:messageData replyHandler:replyHandler];
}
- (void)handleReceivedData:(NSData *)messageData replyHandler:(void (^)(NSData *))replyHandler
{
id message = nil;
@try
{
message = [NSKeyedUnarchiver unarchiveObjectWithData:messageData];
}
@catch (NSException *exception)
{
}
if ([message isKindOfClass:[TGBridgeResponse class]])
{
TGBridgeResponse *response = (TGBridgeResponse *)message;
[_signalManager putNext:response toMulticastedPipeForKey:[NSString stringWithFormat:@"%lld", response.subscriptionIdentifier]];
}
else if ([message isKindOfClass:[TGBridgeSubscriptionListRequest class]])
{
[self refreshSubscriptions];
}
}
- (void)session:(WCSession *)session didReceiveApplicationContext:(NSDictionary *)applicationContext
{
TGBridgeContext *context = [[TGBridgeContext alloc] initWithDictionary:applicationContext];
_contextPipe.sink(context);
if (!context.customLocalizationEnabled && TGIsCustomLocalizationActive())
[[TGExtensionDelegate instance] setCustomLocalizationFile:nil];
}
- (void)session:(WCSession *)session didReceiveFile:(WCSessionFile *)file
{
NSString *type = file.metadata[TGBridgeIncomingFileTypeKey];
NSString *identifier = file.metadata[TGBridgeIncomingFileIdentifierKey];
if (identifier == nil)
return;
if ([identifier isEqualToString:@"stickers"])
{
NSURL *stickerPacksURL = [TGBridgeStickersSignals stickerPacksURL];
if ([[NSFileManager defaultManager] fileExistsAtPath:stickerPacksURL.path])
[[NSFileManager defaultManager] removeItemAtURL:stickerPacksURL error:nil];
[[NSFileManager defaultManager] moveItemAtURL:file.fileURL toURL:stickerPacksURL error:nil];
NSArray *stickerPacks = [self readStickerPacks];
OSSpinLockLock(&_stickerPacksLock);
_stickerPacks = stickerPacks;
OSSpinLockUnlock(&_stickerPacksLock);
[_fileSignalManager putNext:stickerPacks toMulticastedPipeForKey:identifier];
}
else if ([identifier isEqualToString:@"localization"])
{
[[TGExtensionDelegate instance] setCustomLocalizationFile:file.fileURL];
}
else if ([identifier isEqualToString:@"presets"])
{
NSURL *presetsURL = [TGBridgePresetsSignals presetsURL];
if ([[NSFileManager defaultManager] fileExistsAtPath:presetsURL.path])
[[NSFileManager defaultManager] removeItemAtURL:presetsURL error:nil];
[[NSFileManager defaultManager] moveItemAtURL:file.fileURL toURL:presetsURL error:nil];
}
else if ([type isEqualToString:TGBridgeIncomingFileTypeImage])
{
NSLog(@"Received image file: %@", identifier);
[[TGExtensionDelegate instance].imageCache cacheFileAtURL:file.fileURL key:identifier synchronous:true unserializeBlock:^id(NSData *data)
{
return data;
} completion:^(NSURL *url)
{
[_fileSignalManager putNext:url toMulticastedPipeForKey:identifier];
}];
}
else if ([type isEqualToString:TGBridgeIncomingFileTypeAudio])
{
NSLog(@"Received audio file: %@", identifier);
[[TGExtensionDelegate instance].audioCache cacheFileAtURL:file.fileURL key:identifier synchronous:true unserializeBlock:nil completion:^(NSURL *url)
{
[_fileSignalManager putNext:url toMulticastedPipeForKey:identifier];
}];
}
}
- (void)sessionReachabilityDidChange:(WCSession *)session
{
bool reachable = session.isReachable;
if (!reachable)
{
TGDispatchAfter(4.5, dispatch_get_main_queue(), ^
{
bool newReachable = session.isReachable;
if (newReachable == reachable && newReachable != _reachable)
{
_reachable = newReachable;
_reachabilityPipe.sink(@(newReachable));
}
});
}
else if (_reachable != reachable)
{
_reachable = reachable;
_reachabilityPipe.sink(@(reachable));
[self ping];
}
if (reachable && !_processingNotification)
[self sendQueuedMessages];
}
- (void)session:(WCSession *)session didReceiveUserInfo:(NSDictionary<NSString *,id> *)userInfo
{
_userInfoPipe.sink(userInfo);
}
- (SSignal *)userInfoSignal
{
return _userInfoPipe.signalProducer();
}
#pragma mark -
- (void)_enqueueMessage:(NSData *)message
{
TGLog(@"[BridgeClient] Enqued failed message");
OSSpinLockLock(&_outgoingQueueLock);
[_outgoingMessageQueue addObject:message];
OSSpinLockUnlock(&_outgoingQueueLock);
}
- (void)sendQueuedMessages
{
OSSpinLockLock(&_outgoingQueueLock);
if (_outgoingMessageQueue.count > 0)
{
TGLog(@"[BridgeClient] Sending queued messages");
for (NSData *messageData in _outgoingMessageQueue)
[self.session sendMessageData:messageData replyHandler:nil errorHandler:nil];
[_outgoingMessageQueue removeAllObjects];
}
OSSpinLockUnlock(&_outgoingQueueLock);
}
#pragma mark -
- (void)ping
{
if (!_isActive || _processingNotification)
return;
TGBridgePing *ping = [[TGBridgePing alloc] initWithSessionId:_sessionId];
NSData *message = [NSKeyedArchiver archivedDataWithRootObject:ping];
[self.session sendMessageData:message replyHandler:^(NSData *replyData)
{
_sentFirstPing = true;
} errorHandler:^(NSError *error)
{
[self _logError:error];
}];
}
- (void)refreshSubscriptions
{
NSArray *activeSubscriptions = [_subscriptions allValues];
NSMutableArray *subscriptions = [[NSMutableArray alloc] init];
for (TGBridgeSubscription *subscription in activeSubscriptions)
{
if (subscription.renewable)
[subscriptions addObject:subscription];
}
TGBridgeSubscriptionList *subscriptionsList = [[TGBridgeSubscriptionList alloc] initWithArray:subscriptions];
NSData *message = [NSKeyedArchiver archivedDataWithRootObject:subscriptionsList];
[self.session sendMessageData:message replyHandler:nil errorHandler:^(NSError *error)
{
[self _logError:error];
}];
}
#pragma mark -
- (void)handleDidBecomeActive
{
_isActive = true;
NSTimeInterval currentTime = [[NSDate date] timeIntervalSinceReferenceDate];
if (_lastForegroundEntry == 0 || currentTime - _lastForegroundEntry > TGBridgeClientWakeInterval)
{
if (_lastForegroundEntry != 0)
[self ping];
_lastForegroundEntry = currentTime;
}
if (_timer == nil)
{
__weak TGBridgeClient *weakSelf = self;
NSTimeInterval interval = _lastForegroundEntry == 0 ? TGBridgeClientTimerInterval : MAX(MIN(TGBridgeClientTimerInterval - currentTime - _lastForegroundEntry, TGBridgeClientTimerInterval), 1);
__block void (^completion)(void) = ^
{
__strong TGBridgeClient *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf ping];
strongSelf->_lastForegroundEntry = [[NSDate date] timeIntervalSinceReferenceDate];
strongSelf->_timer = [[STimer alloc] initWithTimeout:TGBridgeClientTimerInterval repeat:false completion:completion queue:[SQueue mainQueue]];
[strongSelf->_timer start];
};
_timer = [[STimer alloc] initWithTimeout:interval repeat:false completion:completion queue:[SQueue mainQueue]];
[_timer start];
}
}
- (void)handleWillResignActive
{
_isActive = false;
[_timer invalidate];
_timer = nil;
}
#pragma mark -
- (void)updateReachability
{
if (self.session.isReachable && !_reachable)
_reachable = true;
}
- (bool)isServerReachable
{
return _reachable;
}
- (bool)isActuallyReachable
{
return self.session.isReachable;
}
- (SSignal *)actualReachabilitySignal
{
return [[SSignal single:@(self.session.isReachable)] then:_actualReachabilityPipe.signalProducer()];
}
- (SSignal *)reachabilitySignal
{
return [[SSignal single:@(self.session.isReachable)] then:_reachabilityPipe.signalProducer()];
}
- (void)_logError:(NSError *)error
{
NSLog(@"%@", error);
}
#pragma mark -
- (WCSession *)session
{
return [WCSession defaultSession];
}
+ (instancetype)instance
{
static dispatch_once_t onceToken;
static TGBridgeClient *instance;
dispatch_once(&onceToken, ^
{
instance = [[TGBridgeClient alloc] init];
});
return instance;
}
@end