From 9e44c0eae8a5e13bfddd00c7888988cb0bfb7c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Mon, 29 Sep 2014 14:02:42 +0200 Subject: [PATCH] add reachability --- common/Reachability.h | 112 ++++++ common/Reachability.m | 527 ++++++++++++++++++++++++++ include/mbgl/map/map.hpp | 3 + include/mbgl/storage/base_request.hpp | 4 + include/mbgl/storage/file_source.hpp | 2 + include/mbgl/storage/http_request.hpp | 1 + ios/mapbox-gl-cocoa | 2 +- macosx/main.mm | 11 + macosx/mapboxgl-app.gyp | 3 + src/map/map.cpp | 11 + src/storage/base_request.cpp | 4 + src/storage/file_source.cpp | 12 + src/storage/http_request.cpp | 26 +- 13 files changed, 713 insertions(+), 5 deletions(-) create mode 100644 common/Reachability.h create mode 100644 common/Reachability.m diff --git a/common/Reachability.h b/common/Reachability.h new file mode 100644 index 000000000..1cf7d2ece --- /dev/null +++ b/common/Reachability.h @@ -0,0 +1,112 @@ +/* + Copyright (c) 2011, Tony Million. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + */ + +#import +#import + +#import +#import +#import +#import +#import +#import + +/** + * Does ARC support GCD objects? + * It does if the minimum deployment target is iOS 6+ or Mac OS X 8+ + * + * @see http://opensource.apple.com/source/libdispatch/libdispatch-228.18/os/object.h + **/ +#if OS_OBJECT_USE_OBJC +#define NEEDS_DISPATCH_RETAIN_RELEASE 0 +#else +#define NEEDS_DISPATCH_RETAIN_RELEASE 1 +#endif + +/** + * Create NS_ENUM macro if it does not exist on the targeted version of iOS or OS X. + * + * @see http://nshipster.com/ns_enum-ns_options/ + **/ +#ifndef NS_ENUM +#define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type +#endif + +extern NSString *const kReachabilityChangedNotification; + +typedef NS_ENUM(NSInteger, NetworkStatus) { + // Apple NetworkStatus Compatible Names. + NotReachable = 0, + ReachableViaWiFi = 2, + ReachableViaWWAN = 1 +}; + +@class Reachability; + +typedef void (^NetworkReachable)(Reachability * reachability); +typedef void (^NetworkUnreachable)(Reachability * reachability); + +@interface Reachability : NSObject + +@property (nonatomic, copy) NetworkReachable reachableBlock; +@property (nonatomic, copy) NetworkUnreachable unreachableBlock; + + +@property (nonatomic, assign) BOOL reachableOnWWAN; + ++(Reachability*)reachabilityWithHostname:(NSString*)hostname; +// This is identical to the function above, but is here to maintain +//compatibility with Apples original code. (see .m) ++(Reachability*)reachabilityWithHostName:(NSString*)hostname; ++(Reachability*)reachabilityForInternetConnection; ++(Reachability*)reachabilityWithAddress:(const struct sockaddr_in*)hostAddress; ++(Reachability*)reachabilityForLocalWiFi; + +-(Reachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref; + +-(BOOL)startNotifier; +-(void)stopNotifier; + +-(BOOL)isReachable; +-(BOOL)isReachableViaWWAN; +-(BOOL)isReachableViaWiFi; + +// WWAN may be available, but not active until a connection has been established. +// WiFi may require a connection for VPN on Demand. +-(BOOL)isConnectionRequired; // Identical DDG variant. +-(BOOL)connectionRequired; // Apple's routine. +// Dynamic, on demand connection? +-(BOOL)isConnectionOnDemand; +// Is user intervention required? +-(BOOL)isInterventionRequired; + +-(NetworkStatus)currentReachabilityStatus; +-(SCNetworkReachabilityFlags)reachabilityFlags; +-(NSString*)currentReachabilityString; +-(NSString*)currentReachabilityFlags; + +@end diff --git a/common/Reachability.m b/common/Reachability.m new file mode 100644 index 000000000..c6f6388f5 --- /dev/null +++ b/common/Reachability.m @@ -0,0 +1,527 @@ +/* + Copyright (c) 2011, Tony Million. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + */ + +#import "Reachability.h" + + +NSString *const kReachabilityChangedNotification = @"kReachabilityChangedNotification"; + +@interface Reachability () + +@property (nonatomic, assign) SCNetworkReachabilityRef reachabilityRef; + + +#if NEEDS_DISPATCH_RETAIN_RELEASE +@property (nonatomic, assign) dispatch_queue_t reachabilitySerialQueue; +#else +@property (nonatomic, strong) dispatch_queue_t reachabilitySerialQueue; +#endif + + +@property (nonatomic, strong) id reachabilityObject; + +-(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags; +-(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags; + +@end + +static NSString *reachabilityFlags(SCNetworkReachabilityFlags flags) +{ + return [NSString stringWithFormat:@"%c%c %c%c%c%c%c%c%c", +#if TARGET_OS_IPHONE + (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', +#else + 'X', +#endif + (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-', + (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-', + (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', + (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', + (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-']; +} + +// Start listening for reachability notifications on the current run loop +static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) +{ +#pragma unused (target) +#if __has_feature(objc_arc) + Reachability *reachability = ((__bridge Reachability*)info); +#else + Reachability *reachability = ((Reachability*)info); +#endif + + // We probably don't need an autoreleasepool here, as GCD docs state each queue has its own autorelease pool, + // but what the heck eh? + @autoreleasepool + { + [reachability reachabilityChanged:flags]; + } +} + + +@implementation Reachability + +@synthesize reachabilityRef; +@synthesize reachabilitySerialQueue; + +@synthesize reachableOnWWAN; + +@synthesize reachableBlock; +@synthesize unreachableBlock; + +@synthesize reachabilityObject; + +#pragma mark - Class Constructor Methods + ++(Reachability*)reachabilityWithHostName:(NSString*)hostname +{ + return [Reachability reachabilityWithHostname:hostname]; +} + ++(Reachability*)reachabilityWithHostname:(NSString*)hostname +{ + SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [hostname UTF8String]); + if (ref) + { + id reachability = [[self alloc] initWithReachabilityRef:ref]; + +#if __has_feature(objc_arc) + return reachability; +#else + return [reachability autorelease]; +#endif + + } + + return nil; +} + ++(Reachability *)reachabilityWithAddress:(const struct sockaddr_in *)hostAddress +{ + SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress); + if (ref) + { + id reachability = [[self alloc] initWithReachabilityRef:ref]; + +#if __has_feature(objc_arc) + return reachability; +#else + return [reachability autorelease]; +#endif + } + + return nil; +} + ++(Reachability *)reachabilityForInternetConnection +{ + struct sockaddr_in zeroAddress; + bzero(&zeroAddress, sizeof(zeroAddress)); + zeroAddress.sin_len = sizeof(zeroAddress); + zeroAddress.sin_family = AF_INET; + + return [self reachabilityWithAddress:&zeroAddress]; +} + ++(Reachability*)reachabilityForLocalWiFi +{ + struct sockaddr_in localWifiAddress; + bzero(&localWifiAddress, sizeof(localWifiAddress)); + localWifiAddress.sin_len = sizeof(localWifiAddress); + localWifiAddress.sin_family = AF_INET; + // IN_LINKLOCALNETNUM is defined in as 169.254.0.0 + localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); + + return [self reachabilityWithAddress:&localWifiAddress]; +} + + +// Initialization methods + +-(Reachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref +{ + self = [super init]; + if (self != nil) + { + self.reachableOnWWAN = YES; + self.reachabilityRef = ref; + } + + return self; +} + +-(void)dealloc +{ + [self stopNotifier]; + + if(self.reachabilityRef) + { + CFRelease(self.reachabilityRef); + self.reachabilityRef = nil; + } + + self.reachableBlock = nil; + self.unreachableBlock = nil; + +#if !(__has_feature(objc_arc)) + [super dealloc]; +#endif + + +} + +#pragma mark - Notifier Methods + +// Notifier +// NOTE: This uses GCD to trigger the blocks - they *WILL NOT* be called on THE MAIN THREAD +// - In other words DO NOT DO ANY UI UPDATES IN THE BLOCKS. +// INSTEAD USE dispatch_async(dispatch_get_main_queue(), ^{UISTUFF}) (or dispatch_sync if you want) + +-(BOOL)startNotifier +{ + SCNetworkReachabilityContext context = { 0, NULL, NULL, NULL, NULL }; + + // this should do a retain on ourself, so as long as we're in notifier mode we shouldn't disappear out from under ourselves + // woah + self.reachabilityObject = self; + + + + // First, we need to create a serial queue. + // We allocate this once for the lifetime of the notifier. + self.reachabilitySerialQueue = dispatch_queue_create("com.tonymillion.reachability", NULL); + if(self.reachabilitySerialQueue == nil) + { + return NO; + } + +#if __has_feature(objc_arc) + context.info = (__bridge void *)self; +#else + context.info = (void *)self; +#endif + + if (!SCNetworkReachabilitySetCallback(self.reachabilityRef, TMReachabilityCallback, &context)) + { +#ifdef DEBUG + NSLog(@"SCNetworkReachabilitySetCallback() failed: %s", SCErrorString(SCError())); +#endif + + // Clear out the dispatch queue + if(self.reachabilitySerialQueue) + { +#if NEEDS_DISPATCH_RETAIN_RELEASE + dispatch_release(self.reachabilitySerialQueue); +#endif + self.reachabilitySerialQueue = nil; + } + + self.reachabilityObject = nil; + + return NO; + } + + // Set it as our reachability queue, which will retain the queue + if(!SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, self.reachabilitySerialQueue)) + { +#ifdef DEBUG + NSLog(@"SCNetworkReachabilitySetDispatchQueue() failed: %s", SCErrorString(SCError())); +#endif + + // UH OH - FAILURE! + + // First stop, any callbacks! + SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL); + + // Then clear out the dispatch queue. + if(self.reachabilitySerialQueue) + { +#if NEEDS_DISPATCH_RETAIN_RELEASE + dispatch_release(self.reachabilitySerialQueue); +#endif + self.reachabilitySerialQueue = nil; + } + + self.reachabilityObject = nil; + + return NO; + } + + return YES; +} + +-(void)stopNotifier +{ + // First stop, any callbacks! + SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL); + + // Unregister target from the GCD serial dispatch queue. + SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, NULL); + + if(self.reachabilitySerialQueue) + { +#if NEEDS_DISPATCH_RETAIN_RELEASE + dispatch_release(self.reachabilitySerialQueue); +#endif + self.reachabilitySerialQueue = nil; + } + + self.reachabilityObject = nil; +} + +#pragma mark - reachability tests + +// This is for the case where you flick the airplane mode; +// you end up getting something like this: +//Reachability: WR ct----- +//Reachability: -- ------- +//Reachability: WR ct----- +//Reachability: -- ------- +// We treat this as 4 UNREACHABLE triggers - really apple should do better than this + +#define testcase (kSCNetworkReachabilityFlagsConnectionRequired | kSCNetworkReachabilityFlagsTransientConnection) + +-(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags +{ + BOOL connectionUP = YES; + + if(!(flags & kSCNetworkReachabilityFlagsReachable)) + connectionUP = NO; + + if( (flags & testcase) == testcase ) + connectionUP = NO; + +#if TARGET_OS_IPHONE + if(flags & kSCNetworkReachabilityFlagsIsWWAN) + { + // We're on 3G. + if(!self.reachableOnWWAN) + { + // We don't want to connect when on 3G. + connectionUP = NO; + } + } +#endif + + return connectionUP; +} + +-(BOOL)isReachable +{ + SCNetworkReachabilityFlags flags; + + if(!SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) + return NO; + + return [self isReachableWithFlags:flags]; +} + +-(BOOL)isReachableViaWWAN +{ +#if TARGET_OS_IPHONE + + SCNetworkReachabilityFlags flags = 0; + + if(SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + // Check we're REACHABLE + if(flags & kSCNetworkReachabilityFlagsReachable) + { + // Now, check we're on WWAN + if(flags & kSCNetworkReachabilityFlagsIsWWAN) + { + return YES; + } + } + } +#endif + + return NO; +} + +-(BOOL)isReachableViaWiFi +{ + SCNetworkReachabilityFlags flags = 0; + + if(SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + // Check we're reachable + if((flags & kSCNetworkReachabilityFlagsReachable)) + { +#if TARGET_OS_IPHONE + // Check we're NOT on WWAN + if((flags & kSCNetworkReachabilityFlagsIsWWAN)) + { + return NO; + } +#endif + return YES; + } + } + + return NO; +} + + +// WWAN may be available, but not active until a connection has been established. +// WiFi may require a connection for VPN on Demand. +-(BOOL)isConnectionRequired +{ + return [self connectionRequired]; +} + +-(BOOL)connectionRequired +{ + SCNetworkReachabilityFlags flags; + + if(SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + return (flags & kSCNetworkReachabilityFlagsConnectionRequired); + } + + return NO; +} + +// Dynamic, on demand connection? +-(BOOL)isConnectionOnDemand +{ + SCNetworkReachabilityFlags flags; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) && + (flags & (kSCNetworkReachabilityFlagsConnectionOnTraffic | kSCNetworkReachabilityFlagsConnectionOnDemand))); + } + + return NO; +} + +// Is user intervention required? +-(BOOL)isInterventionRequired +{ + SCNetworkReachabilityFlags flags; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) && + (flags & kSCNetworkReachabilityFlagsInterventionRequired)); + } + + return NO; +} + + +#pragma mark - reachability status stuff + +-(NetworkStatus)currentReachabilityStatus +{ + if([self isReachable]) + { + if([self isReachableViaWiFi]) + return ReachableViaWiFi; + +#if TARGET_OS_IPHONE + return ReachableViaWWAN; +#endif + } + + return NotReachable; +} + +-(SCNetworkReachabilityFlags)reachabilityFlags +{ + SCNetworkReachabilityFlags flags = 0; + + if(SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + return flags; + } + + return 0; +} + +-(NSString*)currentReachabilityString +{ + NetworkStatus temp = [self currentReachabilityStatus]; + + if(temp == reachableOnWWAN) + { + // Updated for the fact that we have CDMA phones now! + return NSLocalizedString(@"Cellular", @""); + } + if (temp == ReachableViaWiFi) + { + return NSLocalizedString(@"WiFi", @""); + } + + return NSLocalizedString(@"No Connection", @""); +} + +-(NSString*)currentReachabilityFlags +{ + return reachabilityFlags([self reachabilityFlags]); +} + +#pragma mark - Callback function calls this method + +-(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags +{ + if([self isReachableWithFlags:flags]) + { + if(self.reachableBlock) + { + self.reachableBlock(self); + } + } + else + { + if(self.unreachableBlock) + { + self.unreachableBlock(self); + } + } + + // this makes sure the change notification happens on the MAIN THREAD + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:kReachabilityChangedNotification + object:self]; + }); +} + +#pragma mark - Debug Description + +- (NSString *) description +{ + NSString *description = [NSString stringWithFormat:@"<%@: %#x>", + NSStringFromClass([self class]), (unsigned int) self]; + return description; +} + +@end diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index 85c9e8e6a..a69943aa7 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -123,6 +123,9 @@ public: void toggleDebug(); bool getDebug() const; + // Call this when the network reachability changed. + void setReachability(bool status); + public: inline const TransformState &getState() const { return state; } inline util::ptr getFileSource() const { return fileSource; } diff --git a/include/mbgl/storage/base_request.hpp b/include/mbgl/storage/base_request.hpp index e9e6bfcb4..16ff24faa 100644 --- a/include/mbgl/storage/base_request.hpp +++ b/include/mbgl/storage/base_request.hpp @@ -41,6 +41,10 @@ public: // This function must call notify(). virtual void cancel() = 0; + // This function is called when the request should be reattempted immediately. This is typically + // reaction to a network status change. + virtual void retryImmediately(); + public: const unsigned long thread_id; const std::string path; diff --git a/include/mbgl/storage/file_source.hpp b/include/mbgl/storage/file_source.hpp index 5933aea48..4b17cd29a 100644 --- a/include/mbgl/storage/file_source.hpp +++ b/include/mbgl/storage/file_source.hpp @@ -36,6 +36,8 @@ public: void prepare(std::function fn); + void retryAllPending(); + private: const unsigned long thread_id; diff --git a/include/mbgl/storage/http_request.hpp b/include/mbgl/storage/http_request.hpp index 05e152aeb..ec76f1147 100644 --- a/include/mbgl/storage/http_request.hpp +++ b/include/mbgl/storage/http_request.hpp @@ -25,6 +25,7 @@ public: ~HTTPRequest(); void cancel(); + void retryImmediately(); private: void startCacheRequest(); diff --git a/ios/mapbox-gl-cocoa b/ios/mapbox-gl-cocoa index 83a5dbfaa..bc2c5fe69 160000 --- a/ios/mapbox-gl-cocoa +++ b/ios/mapbox-gl-cocoa @@ -1 +1 @@ -Subproject commit 83a5dbfaac85d16e35aca3acade1b5b151ae600a +Subproject commit bc2c5fe6974fa99ea4816e9e793757cc17066594 diff --git a/macosx/main.mm b/macosx/main.mm index 7f22a32cc..aeb49225c 100644 --- a/macosx/main.mm +++ b/macosx/main.mm @@ -1,6 +1,7 @@ #include "../common/settings_nsuserdefaults.hpp" #include "../common/glfw_view.hpp" #include "../common/nslog_log.hpp" +#include "../common/Reachability.h" #import @@ -75,12 +76,20 @@ int main() { GLFWView view; mbgl::Map map(view); + mbgl::Map *map_ptr = ↦ URLHandler *handler = [[URLHandler alloc] init]; [handler setMap:&map]; NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager]; [appleEventManager setEventHandler:handler andSelector:@selector(handleGetURLEvent:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL]; + // Notify map object when network reachability status changes. + Reachability* reachability = [Reachability reachabilityForInternetConnection]; + reachability.reachableBlock = ^(Reachability *reachability) { + map_ptr->setReachability(true); + }; + [reachability startNotifier]; + // Load settings mbgl::Settings_NSUserDefaults settings; map.setLonLatZoom(settings.longitude, settings.latitude, settings.zoom); @@ -100,6 +109,8 @@ int main() { int ret = view.run(); + [reachability stopNotifier]; + // Save settings map.getLonLatZoom(settings.longitude, settings.latitude, settings.zoom); settings.bearing = map.getBearing(); diff --git a/macosx/mapboxgl-app.gyp b/macosx/mapboxgl-app.gyp index 529da011a..51f591fdd 100644 --- a/macosx/mapboxgl-app.gyp +++ b/macosx/mapboxgl-app.gyp @@ -15,6 +15,8 @@ '../common/platform_nsstring.mm', '../common/glfw_view.hpp', '../common/glfw_view.cpp', + '../common/reachability.h', + '../common/reachability.m', '../common/http_request_baton_cocoa.mm', '../common/osx.mm', '../common/nslog_log.hpp', @@ -33,6 +35,7 @@ ], 'OTHER_LDFLAGS': [ '<@(glfw3_libraries)', + '-framework SystemConfiguration', ], 'SDKROOT': 'macosx', 'INFOPLIST_FILE': 'Info.plist', diff --git a/src/map/map.cpp b/src/map/map.cpp index 05c56c10e..f71ecdb27 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -198,6 +198,17 @@ void Map::cleanup(uv_async_t *async) { map->painter.cleanup(); } +void Map::setReachability(bool reachable) { + // Note: This function may be called from *any* thread. + if (reachable) { + if (fileSource) { + fileSource->prepare([&]() { + fileSource->retryAllPending(); + }); + } + } +} + void Map::render(uv_async_t *async) { Map *map = static_cast(async->data); assert(uv_thread_self() == map->map_thread); diff --git a/src/storage/base_request.cpp b/src/storage/base_request.cpp index fead001da..e87679ae1 100644 --- a/src/storage/base_request.cpp +++ b/src/storage/base_request.cpp @@ -28,6 +28,10 @@ BaseRequest::~BaseRequest() { notify(); } +void BaseRequest::retryImmediately() { + // no-op. override in child class. +} + void BaseRequest::notify() { assert(thread_id == uv_thread_self()); diff --git a/src/storage/file_source.cpp b/src/storage/file_source.cpp index 52a3872cb..370e36f2e 100644 --- a/src/storage/file_source.cpp +++ b/src/storage/file_source.cpp @@ -89,4 +89,16 @@ void FileSource::prepare(std::function fn) { } } +void FileSource::retryAllPending() { + assert(thread_id == uv_thread_self()); + + util::ptr request; + for (const std::pair> &pair : pending) { + if ((request = pair.second.lock())) { + request->retryImmediately(); + } + } + +} + } \ No newline at end of file diff --git a/src/storage/http_request.cpp b/src/storage/http_request.cpp index a60f4b891..4a35dcc51 100644 --- a/src/storage/http_request.cpp +++ b/src/storage/http_request.cpp @@ -177,15 +177,16 @@ void HTTPRequest::handleHTTPResponse(HTTPResponseType responseType, std::unique_ } } +using RetryBaton = std::pair>; + void HTTPRequest::retryHTTPRequest(std::unique_ptr &&res, uint64_t timeout) { assert(uv_thread_self() == thread_id); assert(!backoff_timer); backoff_timer = new uv_timer_t(); uv_timer_init(loop, backoff_timer); - using Store = std::pair>; - backoff_timer->data = new Store(this, std::move(res)); + backoff_timer->data = new RetryBaton(this, std::move(res)); uv_timer_start(backoff_timer, [](uv_timer_t *timer) { - std::unique_ptr pair { static_cast(timer->data) }; + std::unique_ptr pair { static_cast(timer->data) }; pair->first->startHTTPRequest(std::move(pair->second)); pair->first->backoff_timer = nullptr; uv_timer_stop(timer); @@ -218,13 +219,30 @@ void HTTPRequest::removeCacheBaton() { void HTTPRequest::removeBackoffTimer() { assert(uv_thread_self() == thread_id); if (backoff_timer) { - delete static_cast> *>(backoff_timer->data); + delete static_cast(backoff_timer->data); uv_timer_stop(backoff_timer); uv_close((uv_handle_t *)backoff_timer, [](uv_handle_t *handle) { delete (uv_timer_t *)handle; }); backoff_timer = nullptr; } } +void HTTPRequest::retryImmediately() { + assert(uv_thread_self() == thread_id); + if (!cache_baton && !http_baton) { + if (backoff_timer) { + // Retry immediately. + uv_timer_stop(backoff_timer); + std::unique_ptr pair { static_cast(backoff_timer->data) }; + assert(pair->first == this); + startHTTPRequest(std::move(pair->second)); + uv_close((uv_handle_t *)backoff_timer, [](uv_handle_t *handle) { delete (uv_timer_t *)handle; }); + backoff_timer = nullptr; + } else { + assert(!"We should always have a backoff_timer when there are no batons"); + } + } +} + void HTTPRequest::cancel() { assert(uv_thread_self() == thread_id); removeCacheBaton();