From da464a32865e548c7d34d1bca9902f60af412fdc Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 7 Aug 2018 14:02:38 +0300 Subject: [PATCH] Current network type detection Small changes in Postbox API --- TelegramCore.xcodeproj/project.pbxproj | 18 + TelegramCore/Account.swift | 7 + TelegramCore/AccountStateReset.swift | 2 +- .../DeleteMessagesInteractively.swift | 2 +- TelegramCore/NetworkType.swift | 254 +++++++++++++ TelegramCore/Reachability.h | 66 ++++ TelegramCore/Reachability.m | 350 ++++++++++++++++++ TelegramCore/TelegramCoreIncludes.h | 1 + 8 files changed, 698 insertions(+), 2 deletions(-) create mode 100644 TelegramCore/NetworkType.swift create mode 100644 TelegramCore/Reachability.h create mode 100644 TelegramCore/Reachability.m diff --git a/TelegramCore.xcodeproj/project.pbxproj b/TelegramCore.xcodeproj/project.pbxproj index 085d506e53..ac9a30ff3c 100644 --- a/TelegramCore.xcodeproj/project.pbxproj +++ b/TelegramCore.xcodeproj/project.pbxproj @@ -397,6 +397,12 @@ D08774FE1E3E3A3500A97350 /* GlobalNotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08774FD1E3E3A3500A97350 /* GlobalNotificationSettings.swift */; }; D08984F22114B97400918162 /* ClearCloudDrafts.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08984F12114B97400918162 /* ClearCloudDrafts.swift */; }; D08984F32114B97400918162 /* ClearCloudDrafts.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08984F12114B97400918162 /* ClearCloudDrafts.swift */; }; + D08984F521187ECA00918162 /* NetworkType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08984F421187ECA00918162 /* NetworkType.swift */; }; + D08984F621187ECA00918162 /* NetworkType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08984F421187ECA00918162 /* NetworkType.swift */; }; + D08984F92118816A00918162 /* Reachability.h in Headers */ = {isa = PBXBuildFile; fileRef = D08984F72118816900918162 /* Reachability.h */; }; + D08984FA2118816A00918162 /* Reachability.h in Headers */ = {isa = PBXBuildFile; fileRef = D08984F72118816900918162 /* Reachability.h */; }; + D08984FB2118816A00918162 /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = D08984F82118816A00918162 /* Reachability.m */; }; + D08984FC2118816A00918162 /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = D08984F82118816A00918162 /* Reachability.m */; }; D08CAA7D1ED77EE90000FDA8 /* LocalizationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08CAA7C1ED77EE90000FDA8 /* LocalizationSettings.swift */; }; D08CAA7E1ED77EE90000FDA8 /* LocalizationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08CAA7C1ED77EE90000FDA8 /* LocalizationSettings.swift */; }; D08CAA801ED80ED20000FDA8 /* SuggestedLocalizationEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08CAA7F1ED80ED20000FDA8 /* SuggestedLocalizationEntry.swift */; }; @@ -950,6 +956,9 @@ D08774FB1E3E39F600A97350 /* ManagedGlobalNotificationSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedGlobalNotificationSettings.swift; sourceTree = ""; }; D08774FD1E3E3A3500A97350 /* GlobalNotificationSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlobalNotificationSettings.swift; sourceTree = ""; }; D08984F12114B97400918162 /* ClearCloudDrafts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearCloudDrafts.swift; sourceTree = ""; }; + D08984F421187ECA00918162 /* NetworkType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkType.swift; sourceTree = ""; }; + D08984F72118816900918162 /* Reachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Reachability.h; sourceTree = ""; }; + D08984F82118816A00918162 /* Reachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Reachability.m; sourceTree = ""; }; D08CAA7C1ED77EE90000FDA8 /* LocalizationSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalizationSettings.swift; sourceTree = ""; }; D08CAA7F1ED80ED20000FDA8 /* SuggestedLocalizationEntry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SuggestedLocalizationEntry.swift; sourceTree = ""; }; D08CAA831ED8164B0000FDA8 /* Localization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Localization.swift; sourceTree = ""; }; @@ -1489,6 +1498,7 @@ D03B0D591D631A6900955575 /* Serialization.swift */, D06AFE8820BF66BF00EA5124 /* DeserializeFunctionResponse.swift */, D0F19F6520E6620D00EEC860 /* MultiplexedRequestManager.swift */, + D08984F421187ECA00918162 /* NetworkType.swift */, ); name = Network; sourceTree = ""; @@ -1564,6 +1574,8 @@ D053B3FA1F1651FA00E2D58A /* MonotonicTime.m */, D02609BB20C6EB97006C34AC /* Crypto.h */, D02609BE20C6EC08006C34AC /* Crypto.m */, + D08984F72118816900918162 /* Reachability.h */, + D08984F82118816A00918162 /* Reachability.m */, ); name = "Supporting Files"; sourceTree = ""; @@ -1878,6 +1890,7 @@ D0B843B41DA7FF30005F29E1 /* NBMetadataCore.h in Headers */, D0B843C41DA7FF30005F29E1 /* NBPhoneNumber.h in Headers */, D0B843B31DA7FF30005F29E1 /* NBAsYouTypeFormatter.h in Headers */, + D08984F92118816A00918162 /* Reachability.h in Headers */, D053B3FB1F1651FA00E2D58A /* MonotonicTime.h in Headers */, D0B843C81DA7FF30005F29E1 /* NBPhoneNumberDesc.h in Headers */, D0B843CA1DA7FF30005F29E1 /* NBPhoneNumberUtil.h in Headers */, @@ -1907,6 +1920,7 @@ D050F2571E4A5AC500988324 /* NBMetadataCoreTestMapper.h in Headers */, D050F2591E4A5AC500988324 /* NBNumberFormat.h in Headers */, D050F25D1E4A5AC500988324 /* NBPhoneNumberDefines.h in Headers */, + D08984FA2118816A00918162 /* Reachability.h in Headers */, D050F25B1E4A5AC500988324 /* NBPhoneMetaDataGenerator.h in Headers */, D050F2581E4A5AC500988324 /* NBMetadataHelper.h in Headers */, ); @@ -2169,6 +2183,7 @@ D053B3FE1F16534400E2D58A /* MonotonicTime.swift in Sources */, D0C50E341E93A86600F62E39 /* CallSessionManager.swift in Sources */, D00D34421E6EDD2E0057B307 /* ManagedSynchronizeConsumeMessageContentsOperations.swift in Sources */, + D08984FB2118816A00918162 /* Reachability.m in Sources */, D0DA1D321F7043D50034E892 /* ManagedPendingPeerNotificationSettings.swift in Sources */, D099D7491EEF418D00A3128C /* HistoryViewChannelStateValidation.swift in Sources */, C23BC3871E9BE3CA00D79F92 /* ImportContact.swift in Sources */, @@ -2381,6 +2396,7 @@ D0B843C51DA7FF30005F29E1 /* NBPhoneNumber.m in Sources */, D03B0D0D1D62255C00955575 /* SynchronizePeerReadState.swift in Sources */, D03B0D081D62255C00955575 /* ChannelState.swift in Sources */, + D08984F521187ECA00918162 /* NetworkType.swift in Sources */, C251D7431E65E50500283EDE /* StickerSetInstallation.swift in Sources */, D07047BA1F3DF75500F6A8D4 /* ConsumePersonalMessageAction.swift in Sources */, D07E413F208A769D00FCA8F0 /* ProxyServersStatuses.swift in Sources */, @@ -2424,6 +2440,7 @@ D033FEB11E61EB0200644997 /* PeerReportStatus.swift in Sources */, D0458C891E69B4AB00FB34C1 /* OutgoingContentInfoMessageAttribute.swift in Sources */, D050F26C1E4A5B6D00988324 /* UpdatePeers.swift in Sources */, + D08984F621187ECA00918162 /* NetworkType.swift in Sources */, D050F26D1E4A5B6D00988324 /* CreateGroup.swift in Sources */, D0575AF51E9FFDDE006F2541 /* ManagedSynchronizeSavedGifsOperations.swift in Sources */, D00D34461E6EDD420057B307 /* SynchronizeConsumeMessageContentsOperation.swift in Sources */, @@ -2653,6 +2670,7 @@ D0561DEB1E5754FA00E6B9E9 /* ChannelAdmins.swift in Sources */, D0AD02E41FFFA14800C1DCFF /* PeerLiveLocationsContext.swift in Sources */, D0613FCB1E60440600202CDB /* InvitationLinks.swift in Sources */, + D08984FC2118816A00918162 /* Reachability.m in Sources */, D0B844471DAB91FD005F29E1 /* ManagedServiceViews.swift in Sources */, D0F3A8A91E82CD7D00B4C64C /* UpdatePeerChatInterfaceState.swift in Sources */, D00D343A1E6EC9520057B307 /* TeleramMediaUnsupported.swift in Sources */, diff --git a/TelegramCore/Account.swift b/TelegramCore/Account.swift index cbdb26fa20..bcdd7363c5 100644 --- a/TelegramCore/Account.swift +++ b/TelegramCore/Account.swift @@ -791,6 +791,11 @@ public class Account { return self.networkStateValue.get() } + private let networkTypeValue = Promise() + public var networkType: Signal { + return self.networkTypeValue.get() + } + private let _loggedOut = ValuePromise(false, ignoreRepeated: true) public var loggedOut: Signal { return self._loggedOut.get() @@ -901,6 +906,8 @@ public class Account { } self.networkStateValue.set(networkStateSignal |> distinctUntilChanged) + self.networkTypeValue.set(currentNetworkType()) + let appliedNotificationToken = self.notificationToken.get() |> distinctUntilChanged |> mapToSignal { token -> Signal in diff --git a/TelegramCore/AccountStateReset.swift b/TelegramCore/AccountStateReset.swift index ae785dae77..e8d629d168 100644 --- a/TelegramCore/AccountStateReset.swift +++ b/TelegramCore/AccountStateReset.swift @@ -205,7 +205,7 @@ func accountStateReset(postbox: Postbox, network: Network) -> Signal 1 { var skipHole = false - if let localTopId = transaction.getTopMesssageIndex(peerId: messageId.peerId, namespace: messageId.namespace)?.id { + if let localTopId = transaction.getTopPeerMessageIndex(peerId: messageId.peerId, namespace: messageId.namespace)?.id { if localTopId >= messageId { skipHole = true } diff --git a/TelegramCore/DeleteMessagesInteractively.swift b/TelegramCore/DeleteMessagesInteractively.swift index a6ff649ec1..75c47aecec 100644 --- a/TelegramCore/DeleteMessagesInteractively.swift +++ b/TelegramCore/DeleteMessagesInteractively.swift @@ -61,7 +61,7 @@ public func clearHistoryInteractively(postbox: Postbox, peerId: PeerId) -> Signa return postbox.transaction { transaction -> Void in if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.CloudGroup || peerId.namespace == Namespaces.Peer.CloudChannel { var topTimestamp: Int32? - if let topIndex = transaction.getTopMesssageIndex(peerId: peerId, namespace: Namespaces.Message.Cloud) { + if let topIndex = transaction.getTopPeerMessageIndex(peerId: peerId, namespace: Namespaces.Message.Cloud) { topTimestamp = topIndex.timestamp } cloudChatAddClearHistoryOperation(transaction: transaction, peerId: peerId, explicitTopMessageId: nil) diff --git a/TelegramCore/NetworkType.swift b/TelegramCore/NetworkType.swift new file mode 100644 index 0000000000..8f7a116f48 --- /dev/null +++ b/TelegramCore/NetworkType.swift @@ -0,0 +1,254 @@ +import Foundation +#if os(macOS) +import SwiftSignalKitMac +#else +import SwiftSignalKit +import CoreTelephony +#endif + +import TelegramCorePrivateModule + +#if os(iOS) +public enum CellularNetworkType { + case unknown + case gprs + case edge + case thirdG + case lte +} + +extension CellularNetworkType { + init(accessTechnology: String) { + switch accessTechnology { + case CTRadioAccessTechnologyGPRS: + self = .gprs + case CTRadioAccessTechnologyEdge, CTRadioAccessTechnologyCDMA1x: + self = .edge + case CTRadioAccessTechnologyLTE: + self = .lte + case CTRadioAccessTechnologyWCDMA, CTRadioAccessTechnologyHSDPA, CTRadioAccessTechnologyHSUPA, CTRadioAccessTechnologyCDMAEVDORev0, CTRadioAccessTechnologyCDMAEVDORevA, CTRadioAccessTechnologyCDMAEVDORevB, CTRadioAccessTechnologyeHRPD: + self = .thirdG + default: + self = .unknown + } + } +} + +#endif + +enum InternalNetworkType: Equatable { + case none + case wifi + case cellular +} + +public enum NetworkType: Equatable { + case none + case wifi +#if os(iOS) + case cellular(CellularNetworkType) +#endif +} + +extension NetworkType { +#if os(iOS) + init(internalType: InternalNetworkType, cellularType: CellularNetworkType) { + switch internalType { + case .none: + self = .none + case .wifi: + self = .wifi + case .cellular: + self = .cellular(cellularType) + } + } +#else + init(internalType: InternalNetworkType) { + switch internalType { + case .none: + self = .none + case .wifi, .cellular: + self = .wifi + } + } +#endif +} + +private final class WrappedReachability: NSObject { + @objc private static func threadImpl() { + while true { + RunLoop.current.run(until: .distantFuture) + } + } + + static let thread: Thread = { + let thread = Thread(target: WrappedReachability.self, selector: #selector(WrappedReachability.threadImpl), object: nil) + thread.start() + return thread + }() + + @objc private static func dispatchOnThreadImpl(_ f: @escaping () -> Void) { + f() + } + + private static func dispatchOnThread(_ f: @escaping @convention(block) () -> Void) { + WrappedReachability.perform(#selector(WrappedReachability.dispatchOnThreadImpl(_:)), on: WrappedReachability.thread, with: f, waitUntilDone: false) + } + + private let reachability: Reachability + + let value: ValuePromise + + override init() { + assert(Thread.current === WrappedReachability.thread) + self.reachability = Reachability.forInternetConnection() + let type: InternalNetworkType + switch self.reachability.currentReachabilityStatus() { + case NotReachable: + type = .none + case ReachableViaWiFi: + type = .wifi + case ReachableViaWWAN: + type = .cellular + default: + type = .none + } + self.value = ValuePromise(type) + + super.init() + + self.reachability.reachabilityChanged = { [weak self] status in + WrappedReachability.dispatchOnThread { + guard let strongSelf = self else { + return + } + let internalNetworkType: InternalNetworkType + switch status { + case NotReachable: + internalNetworkType = .none + case ReachableViaWiFi: + internalNetworkType = .wifi + case ReachableViaWWAN: + internalNetworkType = .cellular + default: + internalNetworkType = .none + } + strongSelf.value.set(internalNetworkType) + } + } + self.reachability.startNotifier() + } + + static var valueRef: Unmanaged? + + static func withInstance(_ f: @escaping (WrappedReachability) -> Void) { + WrappedReachability.dispatchOnThread { + if self.valueRef == nil { + self.valueRef = Unmanaged.passRetained(WrappedReachability()) + } + if let valueRef = self.valueRef { + let value = valueRef.takeUnretainedValue() + f(value) + } + } + } +} + +private final class NetworkTypeManagerImpl { + let queue: Queue + let updated: (NetworkType) -> Void + var networkTypeDisposable: Disposable? + var currentNetworkType: InternalNetworkType? + var networkType: NetworkType? + #if os(iOS) + var currentCellularType: CellularNetworkType + var cellularTypeObserver: NSObjectProtocol? + #endif + + init(queue: Queue, updated: @escaping (NetworkType) -> Void) { + self.queue = queue + self.updated = updated + + #if os(iOS) + let telephonyInfo = CTTelephonyNetworkInfo() + let accessTechnology = telephonyInfo.currentRadioAccessTechnology ?? "" + self.currentCellularType = CellularNetworkType(accessTechnology: accessTechnology) + self.cellularTypeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.CTRadioAccessTechnologyDidChange, object: nil, queue: nil, using: { [weak self] notification in + queue.async { + guard let strongSelf = self else { + return + } + let accessTechnology = telephonyInfo.currentRadioAccessTechnology ?? "" + let cellularType = CellularNetworkType(accessTechnology: accessTechnology) + if strongSelf.currentCellularType != cellularType { + strongSelf.currentCellularType = cellularType + + if let currentNetworkType = strongSelf.currentNetworkType { + let networkType = NetworkType(internalType: currentNetworkType, cellularType: cellularType) + + if strongSelf.networkType != networkType { + strongSelf.networkType = networkType + strongSelf.updated(networkType) + } + } + } + } + }) + #endif + + let networkTypeDisposable = MetaDisposable() + self.networkTypeDisposable = networkTypeDisposable + + WrappedReachability.withInstance({ [weak self] impl in + networkTypeDisposable.set((impl.value.get() + |> deliverOn(queue)).start(next: { networkStatus in + guard let strongSelf = self else { + return + } + if strongSelf.currentNetworkType != networkStatus { + strongSelf.currentNetworkType = networkStatus + + let networkType: NetworkType + #if os(iOS) + networkType = NetworkType(internalType: networkStatus, cellularType: strongSelf.currentCellularType) + #else + networkType = NetworkType(internalType: networkStatus) + #endif + if strongSelf.networkType != networkType { + strongSelf.networkType = networkType + updated(networkType) + } + } + })) + }) + } + + func stop() { + self.networkTypeDisposable?.dispose() + #if os(iOS) + if let observer = self.cellularTypeObserver { + NotificationCenter.default.removeObserver(observer, name: NSNotification.Name.CTRadioAccessTechnologyDidChange, object: nil) + } + #endif + } +} + +func currentNetworkType() -> Signal { + return Signal { subscriber in + let queue = Queue() + let disposable = MetaDisposable() + queue.async { + let impl = QueueLocalObject(queue: queue, generate: { + return NetworkTypeManagerImpl(queue: queue, updated: { value in + subscriber.putNext(value) + }) + }) + disposable.set(ActionDisposable { + impl.with({ impl in + impl.stop() + }) + }) + } + return disposable + } +} diff --git a/TelegramCore/Reachability.h b/TelegramCore/Reachability.h new file mode 100644 index 0000000000..c24384906c --- /dev/null +++ b/TelegramCore/Reachability.h @@ -0,0 +1,66 @@ +/* + Copyright (C) 2016 Apple Inc. All Rights Reserved. + See LICENSE.txt for this sample’s licensing information + + Abstract: + Basic demonstration of how to use the SystemConfiguration Reachablity APIs. + */ + +#import +#import +#import + + +typedef enum : NSInteger { + NotReachable = 0, + ReachableViaWiFi, + ReachableViaWWAN +} NetworkStatus; + +#pragma mark IPv6 Support +//Reachability fully support IPv6. For full details, see ReadMe.md. + + +extern NSString *kReachabilityChangedNotification; + + +@interface Reachability : NSObject + +@property (nonatomic, copy) void (^reachabilityChanged)(NetworkStatus status); + +/*! + * Use to check the reachability of a given host name. + */ ++ (instancetype)reachabilityWithHostName:(NSString *)hostName; + +/*! + * Use to check the reachability of a given IP address. + */ ++ (instancetype)reachabilityWithAddress:(const struct sockaddr *)hostAddress; + +/*! + * Checks whether the default route is available. Should be used by applications that do not connect to a particular host. + */ ++ (instancetype)reachabilityForInternetConnection; + + +#pragma mark reachabilityForLocalWiFi +//reachabilityForLocalWiFi has been removed from the sample. See ReadMe.md for more information. +//+ (instancetype)reachabilityForLocalWiFi; + +/*! + * Start listening for reachability notifications on the current run loop. + */ +- (BOOL)startNotifier; +- (void)stopNotifier; + +- (NetworkStatus)currentReachabilityStatus; + +/*! + * WWAN may be available, but not active until a connection has been established. WiFi may require a connection for VPN on Demand. + */ +- (BOOL)connectionRequired; + +@end + + diff --git a/TelegramCore/Reachability.m b/TelegramCore/Reachability.m new file mode 100644 index 0000000000..fd61f0309a --- /dev/null +++ b/TelegramCore/Reachability.m @@ -0,0 +1,350 @@ +/* + Copyright (C) 2016 Apple Inc. All Rights Reserved. + See LICENSE.txt for this sample’s licensing information + + Abstract: + Basic demonstration of how to use the SystemConfiguration Reachablity APIs. + */ + +#import +#import +#import +#import +#import + +#import + +#import "Reachability.h" + +#import +#import + +#pragma mark IPv6 Support +//Reachability fully support IPv6. For full details, see ReadMe.md. + + +NSString *kReachabilityChangedNotification = @"kNetworkReachabilityChangedNotification"; + + +#pragma mark - Supporting functions + +#define kShouldPrintReachabilityFlags 1 + +static void PrintReachabilityFlags(SCNetworkReachabilityFlags flags, const char* comment) +{ +#if kShouldPrintReachabilityFlags + + NSLog(@"Reachability Flag Status: %c%c %c%c%c%c%c%c%c %s\n", + (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', + (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-', + + (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-', + (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', + (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', + (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-', + comment + ); +#endif +} + +@interface ReachabilityAtomic : NSObject +{ + pthread_mutex_t _lock; + pthread_mutexattr_t _attr; + id _value; +} + +@end + +@implementation ReachabilityAtomic + +- (instancetype)initWithValue:(id)value { + self = [super init]; + if (self != nil) { + pthread_mutex_init(&_lock, NULL); + _value = value; + } + return self; +} + +- (void)dealloc { + pthread_mutex_destroy(&_lock); +} + +- (id)swap:(id)newValue { + id previousValue = nil; + pthread_mutex_lock(&_lock); + previousValue = _value; + _value = newValue; + pthread_mutex_unlock(&_lock); + return previousValue; +} + +- (id)value { + id previousValue = nil; + pthread_mutex_lock(&_lock); + previousValue = _value; + pthread_mutex_unlock(&_lock); + + return previousValue; +} + +- (id)modify:(id (^)(id))f { + id newValue = nil; + pthread_mutex_lock(&_lock); + newValue = f(_value); + _value = newValue; + pthread_mutex_unlock(&_lock); + return newValue; +} + +- (id)with:(id (^)(id))f { + id result = nil; + pthread_mutex_lock(&_lock); + result = f(_value); + pthread_mutex_unlock(&_lock); + return result; +} + +@end + +static int32_t nextKey = 1; +static ReachabilityAtomic *contexts() { + static ReachabilityAtomic *instance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[ReachabilityAtomic alloc] initWithValue:@{}]; + }); + return instance; +} + +static void withContext(int32_t key, void (^f)(Reachability *)) { + Reachability *reachability = [contexts() with:^id(NSDictionary *dict) { + return dict[@(key)]; + }]; + f(reachability); +} + +static int32_t addContext(Reachability *context) { + int32_t key = OSAtomicIncrement32(&nextKey); + [contexts() modify:^id(NSMutableDictionary *dict) { + NSMutableDictionary *updatedDict = [[NSMutableDictionary alloc] initWithDictionary:dict]; + updatedDict[@(key)] = context; + return updatedDict; + }]; + return key; +} + +static void removeContext(int32_t key) { + [contexts() modify:^id(NSMutableDictionary *dict) { + NSMutableDictionary *updatedDict = [[NSMutableDictionary alloc] initWithDictionary:dict]; + [updatedDict removeObjectForKey:@(key)]; + return updatedDict; + }]; +} + +static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) +{ +#pragma unused (target, flags) + //NSCAssert(info != NULL, @"info was NULL in ReachabilityCallback"); + //NSCAssert([(__bridge NSObject*) info isKindOfClass: [Reachability class]], @"info was wrong class in ReachabilityCallback"); + + int32_t key = (int32_t)((intptr_t)info); + withContext(key, ^(Reachability *context) { + if ([context isKindOfClass:[Reachability class]] && context.reachabilityChanged != nil) + context.reachabilityChanged(context.currentReachabilityStatus); + }); +} + + +#pragma mark - Reachability implementation + +@implementation Reachability +{ + int32_t _key; + SCNetworkReachabilityRef _reachabilityRef; +} + ++ (instancetype)reachabilityWithHostName:(NSString *)hostName +{ + Reachability* returnValue = NULL; + SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]); + if (reachability != NULL) + { + returnValue= [[self alloc] init]; + if (returnValue != NULL) + { + returnValue->_reachabilityRef = reachability; + } + else { + CFRelease(reachability); + } + } + if (returnValue) { + returnValue->_key = addContext(returnValue); + } + return returnValue; +} + + ++ (instancetype)reachabilityWithAddress:(const struct sockaddr *)hostAddress +{ + SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, hostAddress); + + Reachability* returnValue = NULL; + + if (reachability != NULL) + { + returnValue = [[self alloc] init]; + if (returnValue != NULL) + { + returnValue->_reachabilityRef = reachability; + } + else { + CFRelease(reachability); + } + } + if (returnValue) { + returnValue->_key = addContext(returnValue); + } + return returnValue; +} + + ++ (instancetype)reachabilityForInternetConnection +{ + struct sockaddr_in zeroAddress; + bzero(&zeroAddress, sizeof(zeroAddress)); + zeroAddress.sin_len = sizeof(zeroAddress); + zeroAddress.sin_family = AF_INET; + + return [self reachabilityWithAddress: (const struct sockaddr *) &zeroAddress]; +} + +#pragma mark reachabilityForLocalWiFi +//reachabilityForLocalWiFi has been removed from the sample. See ReadMe.md for more information. +//+ (instancetype)reachabilityForLocalWiFi + + + +#pragma mark - Start and stop notifier + +- (BOOL)startNotifier +{ + BOOL returnValue = NO; + SCNetworkReachabilityContext context = {0, (void *)((intptr_t)_key), NULL, NULL, NULL}; + + if (SCNetworkReachabilitySetCallback(_reachabilityRef, ReachabilityCallback, &context)) + { + if (SCNetworkReachabilityScheduleWithRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode)) + { + returnValue = YES; + } + } + + return returnValue; +} + + +- (void)stopNotifier +{ + if (_reachabilityRef != NULL) + { + SCNetworkReachabilityUnscheduleFromRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + } +} + + +- (void)dealloc +{ + removeContext(_key); + [self stopNotifier]; + if (_reachabilityRef != NULL) + { + CFRelease(_reachabilityRef); + } +} + + +#pragma mark - Network Flag Handling + +- (NetworkStatus)networkStatusForFlags:(SCNetworkReachabilityFlags)flags +{ + PrintReachabilityFlags(flags, "networkStatusForFlags"); + if ((flags & kSCNetworkReachabilityFlagsReachable) == 0) + { + // The target host is not reachable. + return NotReachable; + } + + NetworkStatus returnValue = NotReachable; + + if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0) + { + /* + If the target host is reachable and no connection is required then we'll assume (for now) that you're on Wi-Fi... + */ + returnValue = ReachableViaWiFi; + } + + if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) || + (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0)) + { + /* + ... and the connection is on-demand (or on-traffic) if the calling application is using the CFSocketStream or higher APIs... + */ + + if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0) + { + /* + ... and no [user] intervention is needed... + */ + returnValue = ReachableViaWiFi; + } + } + + if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN) + { + /* + ... but WWAN connections are OK if the calling application is using the CFNetwork APIs. + */ + returnValue = ReachableViaWWAN; + } + + return returnValue; +} + + +- (BOOL)connectionRequired +{ + NSAssert(_reachabilityRef != NULL, @"connectionRequired called with NULL reachabilityRef"); + SCNetworkReachabilityFlags flags; + + if (SCNetworkReachabilityGetFlags(_reachabilityRef, &flags)) + { + return (flags & kSCNetworkReachabilityFlagsConnectionRequired); + } + + return NO; +} + + +- (NetworkStatus)currentReachabilityStatus +{ + NSAssert(_reachabilityRef != NULL, @"currentNetworkStatus called with NULL SCNetworkReachabilityRef"); + NetworkStatus returnValue = NotReachable; + SCNetworkReachabilityFlags flags; + + if (SCNetworkReachabilityGetFlags(_reachabilityRef, &flags)) + { + returnValue = [self networkStatusForFlags:flags]; + } + + return returnValue; +} + + +@end diff --git a/TelegramCore/TelegramCoreIncludes.h b/TelegramCore/TelegramCoreIncludes.h index 455d129a9b..214f0b11aa 100644 --- a/TelegramCore/TelegramCoreIncludes.h +++ b/TelegramCore/TelegramCoreIncludes.h @@ -2,5 +2,6 @@ #define TelegramCoreIncludes_h #import "Crypto.h" +#import "Reachability.h" #endif