summaryrefslogtreecommitdiff
path: root/common
diff options
context:
space:
mode:
authorMike Morris <michael.patrick.morris@gmail.com>2014-10-10 12:24:45 -0400
committerMike Morris <michael.patrick.morris@gmail.com>2014-10-10 12:24:45 -0400
commit2d1219fa5154c489cd856bedd04b84573d45ac04 (patch)
treea8e42e6acd79f73aac228e0fe6876917067db8c4 /common
parent8f6e8eead12c6b2c2de0ce76fa7df39ca2445006 (diff)
parentf390dab0ea7d449bdd89855c84e47f4a07606fe4 (diff)
downloadqtlocation-mapboxgl-2d1219fa5154c489cd856bedd04b84573d45ac04.tar.gz
Merge branch 'master' into libuv-0.10-headless-display
Conflicts: common/curl_request.cpp common/glfw_view.cpp common/glfw_view.hpp include/mbgl/platform/request.hpp ios/mapbox-gl-cocoa setup-libraries.sh src/map/map.cpp src/platform/request.cpp test/fixtures/fixture_request.cpp
Diffstat (limited to 'common')
-rw-r--r--common/Reachability.h112
-rw-r--r--common/Reachability.m527
-rw-r--r--common/foundation_request.h1
-rw-r--r--common/foundation_request.mm115
-rw-r--r--common/glfw_view.cpp8
-rw-r--r--common/glfw_view.hpp1
-rw-r--r--common/headless_view.cpp4
-rw-r--r--common/headless_view.hpp1
-rw-r--r--common/http_request_baton_cocoa.mm149
-rw-r--r--common/http_request_baton_curl.cpp465
-rw-r--r--common/ios.mm21
-rw-r--r--common/linux.cpp12
-rw-r--r--common/osx.mm31
13 files changed, 1330 insertions, 117 deletions
diff --git a/common/Reachability.h b/common/Reachability.h
new file mode 100644
index 0000000000..1cf7d2ecea
--- /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 <Foundation/Foundation.h>
+#import <SystemConfiguration/SystemConfiguration.h>
+
+#import <sys/socket.h>
+#import <netinet/in.h>
+#import <netinet6/in6.h>
+#import <arpa/inet.h>
+#import <ifaddrs.h>
+#import <netdb.h>
+
+/**
+ * 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 0000000000..c6f6388f52
--- /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 <netinet/in.h> 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/common/foundation_request.h b/common/foundation_request.h
deleted file mode 100644
index 0b8c4b8fb0..0000000000
--- a/common/foundation_request.h
+++ /dev/null
@@ -1 +0,0 @@
-#import <Foundation/Foundation.h>
diff --git a/common/foundation_request.mm b/common/foundation_request.mm
deleted file mode 100644
index b7eafdb96c..0000000000
--- a/common/foundation_request.mm
+++ /dev/null
@@ -1,115 +0,0 @@
-#import "foundation_request.h"
-
-#include "TargetConditionals.h"
-#if TARGET_OS_IPHONE
-#import <UIKit/UIKit.h>
-#include <atomic>
-#endif
-
-#include <memory>
-#include <string>
-#include <functional>
-#include <mbgl/platform/request.hpp>
-#include <mbgl/platform/platform.hpp>
-#include <mbgl/util/std.hpp>
-#include <mbgl/util/uv.hpp>
-
-dispatch_once_t request_initialize = 0;
-NSURLSession *session = nullptr;
-
-#if TARGET_OS_IPHONE
-std::atomic<int> active_tasks;
-#endif
-
-
-// We're using a child class to make sure ARC is working correctly, as well as to add activity
-// indicators on iOS.
-class FoundationRequest : public mbgl::platform::Request {
-public:
- FoundationRequest(const std::string &url,
- std::function<void(mbgl::platform::Response *)> callback,
- std::shared_ptr<uv::loop> loop)
- : Request(url, callback, loop) {
-#if TARGET_OS_IPHONE
- active_tasks++;
- dispatch_async(dispatch_get_main_queue(), ^(void) {
- [[UIApplication sharedApplication]
- setNetworkActivityIndicatorVisible:(active_tasks > 0)];
- });
-#endif
- }
-
- ~FoundationRequest() {
-#if TARGET_OS_IPHONE
- active_tasks--;
- dispatch_async(dispatch_get_main_queue(), ^(void) {
- [[UIApplication sharedApplication]
- setNetworkActivityIndicatorVisible:(active_tasks > 0)];
- });
-#endif
- }
-
- NSURLSessionDataTask *task = nullptr;
-};
-
-std::shared_ptr<mbgl::platform::Request>
-mbgl::platform::request_http(const std::string &url,
- std::function<void(Response *)> callback,
- std::shared_ptr<uv::loop> loop) {
- dispatch_once(&request_initialize, ^{
- NSURLSessionConfiguration *sessionConfig =
- [NSURLSessionConfiguration defaultSessionConfiguration];
- sessionConfig.timeoutIntervalForResource = 30;
- sessionConfig.HTTPMaximumConnectionsPerHost = 8;
- sessionConfig.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
-
- session = [NSURLSession sessionWithConfiguration:sessionConfig];
-
-#if TARGET_OS_IPHONE
- active_tasks = 0;
-#endif
- });
-
- std::shared_ptr<FoundationRequest> req =
- std::make_shared<FoundationRequest>(url, callback, loop);
-
- // Note that we are creating a new shared_ptr pointer(!) to make sure there is at least one
- // shared_ptr in existence while the NSURLSession is loading our data. We are making sure in the
- // callback that this pointer gets destroyed again.
- std::shared_ptr<Request> *req_ptr = new std::shared_ptr<Request>(req);
-
- NSURLSessionDataTask *task = [session
- dataTaskWithURL:[NSURL URLWithString:@(url.c_str())]
- completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
- if ([error code] == NSURLErrorCancelled) {
- // We intentionally cancelled this request. Make sure we clear the shared_ptr to resolve
- // the circular reference. We're referencing this shared_ptr by value so that the object
- // stays around until this completion handler is invoked.
- delete req_ptr;
-
- return;
- }
-
- if (!error && [response isKindOfClass:[NSHTTPURLResponse class]]) {
- (*req_ptr)->res->code = [(NSHTTPURLResponse *)response statusCode];
- (*req_ptr)->res->body = {(const char *)[data bytes], [data length]};
- } else {
- (*req_ptr)->res->error_message = [[error localizedDescription] UTF8String];
- }
-
- (*req_ptr)->complete();
-
- delete req_ptr;
- }];
-
- req->task = task;
-
- [task resume];
- return req;
-}
-
-void mbgl::platform::cancel_request_http(const std::shared_ptr<Request> &req) {
- if (req) {
- [((FoundationRequest *)(req.get()))->task cancel];
- }
-}
diff --git a/common/glfw_view.cpp b/common/glfw_view.cpp
index 0908786bb6..120faf4df1 100644
--- a/common/glfw_view.cpp
+++ b/common/glfw_view.cpp
@@ -186,7 +186,9 @@ int GLFWView::run() {
glfwWaitEvents();
}
- map->stop();
+ map->stop([](void *) {
+ glfwWaitEvents();
+ });
return 0;
}
@@ -199,6 +201,10 @@ void GLFWView::make_inactive() {
glfwMakeContextCurrent(nullptr);
}
+void GLFWView::notify() {
+ glfwPostEmptyEvent();
+}
+
void GLFWView::swap() {
glfwPostEmptyEvent();
diff --git a/common/glfw_view.hpp b/common/glfw_view.hpp
index 04085f7750..6e91c1125e 100644
--- a/common/glfw_view.hpp
+++ b/common/glfw_view.hpp
@@ -18,6 +18,7 @@ public:
void swap();
void make_active();
void make_inactive();
+ void notify();
void notify_map_change(mbgl::MapChange change, mbgl::timestamp delay = 0);
static void key(GLFWwindow *window, int key, int scancode, int action, int mods);
diff --git a/common/headless_view.cpp b/common/headless_view.cpp
index 3f945ee6aa..c2084ac90d 100644
--- a/common/headless_view.cpp
+++ b/common/headless_view.cpp
@@ -161,6 +161,10 @@ HeadlessView::~HeadlessView() {
#endif
}
+void HeadlessView::notify() {
+ // no-op
+}
+
void HeadlessView::notify_map_change(mbgl::MapChange /*change*/, mbgl::timestamp /*delay*/) {
// no-op
}
diff --git a/common/headless_view.hpp b/common/headless_view.hpp
index 600e9d51fa..d2fe75382a 100644
--- a/common/headless_view.hpp
+++ b/common/headless_view.hpp
@@ -28,6 +28,7 @@ public:
void resize(uint16_t width, uint16_t height, float pixelRatio);
const std::unique_ptr<uint32_t[]> readPixels();
+ void notify();
void notify_map_change(MapChange change, timestamp delay = 0);
void make_active();
void make_inactive();
diff --git a/common/http_request_baton_cocoa.mm b/common/http_request_baton_cocoa.mm
new file mode 100644
index 0000000000..a9992fad8c
--- /dev/null
+++ b/common/http_request_baton_cocoa.mm
@@ -0,0 +1,149 @@
+#include <mbgl/storage/http_request_baton.hpp>
+#include <mbgl/util/std.hpp>
+#include <mbgl/util/parsedate.h>
+
+#include <uv.h>
+
+#include <mbgl/util/uv.hpp>
+
+#import <Foundation/Foundation.h>
+#include <ctime>
+#include <xlocale.h>
+
+namespace mbgl {
+
+dispatch_once_t request_initialize = 0;
+NSURLSession *session = nullptr;
+
+void HTTPRequestBaton::start(const util::ptr<HTTPRequestBaton> &ptr) {
+ assert(uv_thread_self() == ptr->thread_id);
+
+ // Starts the request.
+ util::ptr<HTTPRequestBaton> baton = ptr;
+
+ // Create a C locale
+ static locale_t locale = newlocale(LC_ALL_MASK, nullptr, nullptr);
+
+ dispatch_once(&request_initialize, ^{
+ NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
+ sessionConfig.timeoutIntervalForResource = 30;
+ sessionConfig.HTTPMaximumConnectionsPerHost = 8;
+ sessionConfig.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
+ sessionConfig.URLCache = nil;
+
+ session = [NSURLSession sessionWithConfiguration:sessionConfig];
+ });
+
+ NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@(baton->path.c_str())]];
+ if (baton->response) {
+ if (!baton->response->etag.empty()) {
+ [request addValue:@(baton->response->etag.c_str()) forHTTPHeaderField:@"If-None-Match"];
+ } else if (baton->response->modified) {
+ const time_t modified = baton->response->modified;
+ struct tm *timeinfo = std::gmtime(&modified);
+ char buffer[32];
+ strftime_l(buffer, 32, "%a, %d %b %Y %H:%M:%S GMT", timeinfo, locale);
+ [request addValue:@(buffer) forHTTPHeaderField:@"If-Modified-Since"];
+ }
+ }
+
+ NSURLSessionDataTask *task = [session dataTaskWithRequest:request
+ completionHandler:^(NSData *data, NSURLResponse *res, NSError *error) {
+ if (error) {
+ if ([error code] == NSURLErrorCancelled) {
+ // The response code remains at 0 to indicate cancelation.
+ // In addition, we don't need any response object.
+ baton->response.reset();
+ baton->type = HTTPResponseType::Canceled;
+ } else {
+ // TODO: Use different codes for host not found, timeout, invalid URL etc.
+ // These can be categorized in temporary and permanent errors.
+ baton->response = std::make_unique<Response>();
+ baton->response->code = [(NSHTTPURLResponse *)res statusCode];
+ baton->response->message = [[error localizedDescription] UTF8String];
+
+ switch ([error code]) {
+ case NSURLErrorBadServerResponse: // 5xx errors
+ baton->type = HTTPResponseType::TemporaryError;
+ break;
+
+ case NSURLErrorTimedOut:
+ case NSURLErrorUserCancelledAuthentication:
+ baton->type = HTTPResponseType::SingularError; // retry immediately
+ break;
+
+ case NSURLErrorNetworkConnectionLost:
+ case NSURLErrorCannotFindHost:
+ case NSURLErrorCannotConnectToHost:
+ case NSURLErrorDNSLookupFailed:
+ case NSURLErrorNotConnectedToInternet:
+ case NSURLErrorInternationalRoamingOff:
+ case NSURLErrorCallIsActive:
+ case NSURLErrorDataNotAllowed:
+ baton->type = HTTPResponseType::ConnectionError;
+ break;
+
+ default:
+ baton->type = HTTPResponseType::PermanentError;
+ }
+ }
+ } else if ([res isKindOfClass:[NSHTTPURLResponse class]]) {
+ const long code = [(NSHTTPURLResponse *)res statusCode];
+ if (code == 304) {
+ // Assume a Response object already exists.
+ assert(baton->response);
+ } else {
+ baton->response = std::make_unique<Response>();
+ baton->response->code = code;
+ baton->response->data = {(const char *)[data bytes], [data length]};
+ }
+
+ if (code == 304) {
+ baton->type = HTTPResponseType::NotModified;
+ } else if (code == 200) {
+ baton->type = HTTPResponseType::Successful;
+ } else {
+ baton->type = HTTPResponseType::PermanentError;
+ }
+
+ NSDictionary *headers = [(NSHTTPURLResponse *)res allHeaderFields];
+ NSString *cache_control = [headers objectForKey:@"Cache-Control"];
+ if (cache_control) {
+ baton->response->expires = Response::parseCacheControl([cache_control UTF8String]);
+ }
+
+ NSString *last_modified = [headers objectForKey:@"Last-Modified"];
+ if (last_modified) {
+ baton->response->modified = parse_date([last_modified UTF8String]);
+ }
+
+ NSString *etag = [headers objectForKey:@"ETag"];
+ if (etag) {
+ baton->response->etag = [etag UTF8String];
+ }
+ } else {
+ // This should never happen.
+ baton->type = HTTPResponseType::PermanentError;
+ baton->response = std::make_unique<Response>();
+ baton->response->code = -1;
+ baton->response->message = "response class is not NSHTTPURLResponse";
+ }
+
+ uv_async_send(baton->async);
+ }];
+
+ [task resume];
+
+ baton->ptr = const_cast<void *>(CFBridgingRetain(task));
+}
+
+void HTTPRequestBaton::stop(const util::ptr<HTTPRequestBaton> &ptr) {
+ assert(uv_thread_self() == ptr->thread_id);
+ assert(ptr->ptr);
+
+ NSURLSessionDataTask *task = CFBridgingRelease(ptr->ptr);
+ ptr->ptr = nullptr;
+ [task cancel];
+}
+
+}
diff --git a/common/http_request_baton_curl.cpp b/common/http_request_baton_curl.cpp
new file mode 100644
index 0000000000..772aeb86e1
--- /dev/null
+++ b/common/http_request_baton_curl.cpp
@@ -0,0 +1,465 @@
+#include <mbgl/storage/http_request_baton.hpp>
+#include <mbgl/util/uv-messenger.h>
+#include <mbgl/util/std.hpp>
+#include <mbgl/util/ptr.hpp>
+
+#include <uv.h>
+#include <curl/curl.h>
+
+#include <queue>
+#include <cassert>
+#include <cstring>
+#include <ctime>
+#include <xlocale.h>
+
+// This file contains code from http://curl.haxx.se/libcurl/c/multi-uv.html:
+
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2013, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+/* Example application code using the multi socket interface to download
+ multiple files at once, but instead of using curl_multi_perform and
+ curl_multi_wait, which uses select(), we use libuv.
+ It supports epoll, kqueue, etc. on unixes and fast IO completion ports on
+ Windows, which means, it should be very fast on all platforms..
+
+ Written by Clemens Gruber, based on an outdated example from uvbook and
+ some tests from libuv.
+
+ Requires libuv and (of course) libcurl.
+
+ See http://nikhilm.github.com/uvbook/ for more information on libuv.
+*/
+
+// Handles the request thread + messaging to the thread.
+static uv_once_t once;
+static uv_loop_t loop;
+static uv_messenger_t start_messenger;
+static uv_messenger_t stop_messenger;
+static uv_thread_t thread;
+static unsigned long thread_id;
+
+// Used as the CURL timer function to periodically check for socket updates.
+static uv_timer_t timeout;
+
+// CURL multi handle that we use to request multiple URLs at the same time, without having to block
+// and spawn threads.
+static CURLM *multi = nullptr;
+
+// CURL share handles are used for sharing session state (e.g.)
+static uv_mutex_t share_mutex;
+static CURLSH *share = nullptr;
+
+// A queue that we use for storing resuable CURL easy handles to avoid creating and destroying them
+// all the time.
+static std::queue<CURL *> handles;
+
+namespace mbgl {
+
+struct CURLContext {
+ util::ptr<HTTPRequestBaton> baton;
+ uv_poll_t *poll_handle = nullptr;
+ curl_socket_t sockfd = 0;
+ curl_slist *headers = nullptr;
+
+ CURLContext(const util::ptr<HTTPRequestBaton> &baton_) : baton(baton_) {
+ }
+
+ ~CURLContext() {
+ // We are destructing the poll handle in a CURL callback already.
+ assert(!poll_handle);
+
+ // Once the CURLContext gets destroyed, CURL doesn't need any headers anymore.
+ if (headers) {
+ curl_slist_free_all(headers);
+ headers = nullptr;
+ }
+ }
+};
+
+// Locks the CURL share handle
+void curl_share_lock(CURL *, curl_lock_data, curl_lock_access, void *) {
+ uv_mutex_lock(&share_mutex);
+}
+
+// Unlocks the CURL share handle
+void curl_share_unlock(CURL *, curl_lock_data, void *) {
+ uv_mutex_unlock(&share_mutex);
+}
+
+// This function must run in the CURL thread.
+// It is either called when the request is completed, or when we try to cancel the request.
+void finish_request(const util::ptr<HTTPRequestBaton> &baton) {
+ assert(uv_thread_self() == thread_id);
+ if (baton->ptr) {
+ CURL *handle = (CURL *)baton->ptr;
+ CURLMcode error = curl_multi_remove_handle(multi, handle);
+ if (error != CURLM_OK) {
+ baton->response = std::make_unique<Response>();
+ baton->response->code = -1;
+ baton->response->message = curl_multi_strerror(error);
+ }
+
+ // Destroy the shared pointer. We still have one pointing to it
+ CURLContext *context = nullptr;
+ curl_easy_getinfo(handle, CURLINFO_PRIVATE, (char *)&context);
+ curl_easy_setopt(handle, CURLOPT_PRIVATE, nullptr);
+ delete context;
+
+ // TODO: delete the headers object again.
+
+ curl_easy_reset(handle);
+ handles.push(handle);
+ baton->ptr = nullptr;
+ }
+}
+
+void curl_perform(uv_poll_t *req, int, int events) {
+ int running_handles;
+ int flags = 0;
+ CURLContext *context = (CURLContext *)req->data;
+ CURLMsg *message;
+ int pending;
+
+ uv_timer_stop(&timeout);
+
+ if (events & UV_READABLE) {
+ flags |= CURL_CSELECT_IN;
+ }
+ if (events & UV_WRITABLE) {
+ flags |= CURL_CSELECT_OUT;
+ }
+
+ curl_multi_socket_action(multi, context->sockfd, flags, &running_handles);
+
+ while ((message = curl_multi_info_read(multi, &pending))) {
+ switch (message->msg) {
+ case CURLMSG_DONE: {
+ CURLContext *context = nullptr;
+ curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&context);
+
+ // Make a copy so that the Baton stays around even after we are calling finish_request
+ util::ptr<HTTPRequestBaton> baton = context->baton;
+
+ // Add human-readable error code
+ if (message->data.result != CURLE_OK) {
+ baton->response->message = curl_easy_strerror(message->data.result);
+ baton->response->code = -1;
+
+ switch (message->data.result) {
+ case CURLE_COULDNT_RESOLVE_PROXY:
+ case CURLE_COULDNT_RESOLVE_HOST:
+ case CURLE_COULDNT_CONNECT:
+ baton->type = HTTPResponseType::ConnectionError;
+ break;
+
+ case CURLE_OPERATION_TIMEDOUT:
+ baton->type = HTTPResponseType::TemporaryError;
+ break;
+
+ default:
+ baton->type = HTTPResponseType::PermanentError;
+ }
+ } else {
+ long code = 0;
+ curl_easy_getinfo(message->easy_handle, CURLINFO_RESPONSE_CODE, &code);
+
+ if (code != 304) {
+ baton->response->code = code;
+ }
+
+ if (code == 304) {
+ baton->type = HTTPResponseType::NotModified;
+ } else if (code == 200) {
+ baton->type = HTTPResponseType::Successful;
+ } else if (code >= 500 && code < 600) {
+ baton->type = HTTPResponseType::TemporaryError;
+ } else if (code >= 400 && code < 500) {
+ baton->type = HTTPResponseType::PermanentError;
+ } else {
+ assert(!"code must be either 200 or 304");
+ }
+ }
+
+ // We're currently in the CURL request thread.
+ finish_request(baton);
+
+ if (baton->async) {
+ uv_async_send(baton->async);
+ baton->async = nullptr;
+ }
+
+ break;
+ }
+
+ default:
+ // This should never happen, because there are no other message types.
+ throw std::runtime_error("CURLMSG returned unknown message type");
+ }
+ }
+}
+
+int handle_socket(CURL *handle, curl_socket_t sockfd, int action, void *, void *socketp) {
+ CURLContext *context = nullptr;
+ curl_easy_getinfo(handle, CURLINFO_PRIVATE, (char *)&context);
+
+ if (!socketp && action != CURL_POLL_REMOVE) {
+ // We haven't initialized the socket yet, so we need to do this now.
+ assert(context->sockfd == 0);
+ context->sockfd = sockfd;
+ assert(!context->poll_handle);
+ context->poll_handle = new uv_poll_t;
+ uv_poll_init_socket(&loop, context->poll_handle, sockfd);
+ context->poll_handle->data = context;
+ curl_multi_assign(multi, sockfd, context);
+ }
+
+ if (context) {
+ if (action == CURL_POLL_IN || action == CURL_POLL_INOUT) {
+ uv_poll_start(context->poll_handle, UV_READABLE, curl_perform);
+ }
+ if (action == CURL_POLL_OUT || action == CURL_POLL_INOUT) {
+ uv_poll_start(context->poll_handle, UV_WRITABLE, curl_perform);
+ }
+ if (action == CURL_POLL_REMOVE && socketp) {
+ uv_poll_stop(context->poll_handle);
+ uv_close((uv_handle_t *)context->poll_handle, [](uv_handle_t *handle) {
+ delete (uv_poll_t *)handle;
+ });
+ context->poll_handle = nullptr;
+ curl_multi_assign(multi, sockfd, NULL);
+ }
+ }
+
+ return 0;
+}
+
+void on_timeout(uv_timer_t *) {
+ int running_handles;
+ CURLMcode error = curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0, &running_handles);
+ if (error != CURLM_OK) {
+ throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(error));
+ }
+}
+
+void start_timeout(CURLM *, long timeout_ms, void *) {
+ if (timeout_ms <= 0) {
+ on_timeout(&timeout);
+ } else {
+ uv_timer_start(&timeout, on_timeout, timeout_ms, 0);
+ }
+}
+
+void thread_init(void *) {
+#ifdef __APPLE__
+ pthread_setname_np("CURL");
+#endif
+ thread_id = uv_thread_self();
+
+ uv_timer_init(&loop, &timeout);
+
+ CURLSHcode share_error;
+ share = curl_share_init();
+
+ share_error = curl_share_setopt(share, CURLSHOPT_LOCKFUNC, curl_share_lock);
+ if (share_error != CURLSHE_OK) {
+ throw std::runtime_error(std::string("CURL share error: ") + curl_share_strerror(share_error));
+ }
+
+ share_error = curl_share_setopt(share, CURLSHOPT_UNLOCKFUNC, curl_share_unlock);
+ if (share_error != CURLSHE_OK) {
+ throw std::runtime_error(std::string("CURL share error: ") + curl_share_strerror(share_error));
+ }
+
+
+ CURLMcode multi_error;
+ multi = curl_multi_init();
+
+ multi_error = curl_multi_setopt(multi, CURLMOPT_SOCKETFUNCTION, handle_socket);
+ if (multi_error != CURLM_OK) {
+ throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(multi_error));
+ }
+ multi_error = curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, start_timeout);
+ if (multi_error != CURLM_OK) {
+ throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(multi_error));
+
+ }
+
+ // Main event loop. This will not return until the request loop is terminated.
+ uv_run(&loop, UV_RUN_DEFAULT);
+
+ curl_multi_cleanup(multi);
+ multi = nullptr;
+
+ curl_share_cleanup(share);
+ share = nullptr;
+
+ thread_id = -1;
+}
+
+// This function is called when we have new data for a request. We just append it to the string
+// containing the previous data.
+size_t curl_write_cb(void *const contents, const size_t size, const size_t nmemb, void *const userp) {
+ ((std::string *)userp)->append((char *)contents, size * nmemb);
+ return size * nmemb;
+}
+
+// Compares the beginning of the (non-zero-terminated!) data buffer with the (zero-terminated!)
+// header string. If the data buffer contains the header string at the beginning, it returns
+// the length of the header string == begin of the value, otherwise it returns npos.
+// The comparison of the header is ASCII-case-insensitive.
+size_t header_matches(const char *const header, const char *const buffer, const size_t length) {
+ const size_t header_length = strlen(header);
+ if (length < header_length) return std::string::npos;
+ size_t i = 0;
+ while (i < length && i < header_length && std::tolower(buffer[i]) == header[i]) {
+ i++;
+ }
+ return i == header_length ? i : std::string::npos;
+}
+
+size_t curl_header_cb(char * const buffer, const size_t size, const size_t nmemb, void *const userp) {
+ const size_t length = size * nmemb;
+
+ Response *response = static_cast<Response *>(userp);
+
+ size_t begin = std::string::npos;
+ if ((begin = header_matches("last-modified: ", buffer, length)) != std::string::npos) {
+ // Always overwrite the modification date; We might already have a value here from the
+ // Date header, but this one is more accurate.
+ const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n
+ response->modified = curl_getdate(value.c_str(), nullptr);
+ } else if ((begin = header_matches("etag: ", buffer, length)) != std::string::npos) {
+ response->etag = { buffer + begin, length - begin - 2 }; // remove \r\n
+ } else if ((begin = header_matches("cache-control: ", buffer, length)) != std::string::npos) {
+ const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n
+ response->expires = Response::parseCacheControl(value.c_str());
+ }
+
+ return length;
+}
+
+// This function must run in the CURL thread.
+void start_request(void *const ptr) {
+ assert(uv_thread_self() == thread_id);
+ std::unique_ptr<util::ptr<HTTPRequestBaton>> baton_guard { (util::ptr<HTTPRequestBaton> *)ptr };
+ util::ptr<HTTPRequestBaton> &baton = *baton_guard.get();
+ assert(baton);
+
+ // Create a C locale
+ static locale_t locale = newlocale(LC_ALL_MASK, nullptr, nullptr);
+
+ CURL *handle = nullptr;
+ if (!handles.empty()) {
+ handle = handles.front();
+ handles.pop();
+ } else {
+ handle = curl_easy_init();
+ }
+
+ baton->ptr = handle;
+
+ // Wrap this in a unique_ptr for now so that it destructs until we assign it the the CURL handle.
+ std::unique_ptr<CURLContext> context = std::make_unique<CURLContext>(baton);
+
+ if (baton->response) {
+ if (!baton->response->etag.empty()) {
+ const std::string header = std::string("If-None-Match: ") + baton->response->etag;
+ context->headers = curl_slist_append(context->headers, header.c_str());
+ } else if (baton->response->modified) {
+ const time_t modified = baton->response->modified;
+ struct tm *timeinfo = std::gmtime(&modified);
+ char buffer[64];
+ strftime_l(buffer, 64, "If-Modified-Since: %a, %d %b %Y %H:%M:%S GMT", timeinfo, locale);
+ context->headers = curl_slist_append(context->headers, buffer);
+ }
+ }
+
+ if (context->headers) {
+ curl_easy_setopt(handle, CURLOPT_HTTPHEADER, context->headers);
+ }
+
+ if (!baton->response) {
+ baton->response = std::make_unique<Response>();
+ }
+
+ // Carry on the shared pointer in the private information of the CURL handle.
+ curl_easy_setopt(handle, CURLOPT_PRIVATE, context.release());
+ curl_easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt");
+ curl_easy_setopt(handle, CURLOPT_URL, baton->path.c_str());
+ curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, curl_write_cb);
+ curl_easy_setopt(handle, CURLOPT_WRITEDATA, &baton->response->data);
+ curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, curl_header_cb);
+ curl_easy_setopt(handle, CURLOPT_HEADERDATA, baton->response.get());
+ curl_easy_setopt(handle, CURLOPT_ACCEPT_ENCODING, "gzip, deflate");
+ curl_easy_setopt(handle, CURLOPT_SHARE, share);
+
+ // Start requesting the information.
+ curl_multi_add_handle(multi, handle);
+}
+
+// This function must run in the CURL thread.
+void stop_request(void *const ptr) {
+ assert(uv_thread_self() == thread_id);
+ std::unique_ptr<util::ptr<HTTPRequestBaton>> baton_guard { (util::ptr<HTTPRequestBaton> *)ptr };
+ util::ptr<HTTPRequestBaton> &baton = *baton_guard.get();
+ assert(baton);
+
+ if (baton->async) {
+ baton->type = HTTPResponseType::Canceled;
+
+ // We can still stop the request because it is still in progress.
+ finish_request(baton);
+
+ uv_async_send(baton->async);
+ baton->async = nullptr;
+ } else {
+ // If the async handle is gone, it means that the actual request has been completed before
+ // we got a chance to cancel it. In this case, this is a no-op. It is likely that
+ // the pointer below is the last lifeline of the HTTPRequestBaton. This means we're going
+ // to delete the HTTPRequestBaton in the current (CURL) thread.
+ }
+}
+
+void create_thread() {
+ uv_mutex_init(&share_mutex);
+ uv_loop_init(&loop);
+ uv_messenger_init(&loop, &start_messenger, start_request);
+ uv_messenger_init(&loop, &stop_messenger, stop_request);
+ uv_thread_create(&thread, thread_init, nullptr);
+}
+
+// This function must be run from the main thread (== where the HTTPRequestBaton was created)
+void HTTPRequestBaton::start(const util::ptr<HTTPRequestBaton> &ptr) {
+ assert(uv_thread_self() == ptr->thread_id);
+ uv_once(&once, create_thread);
+ uv_messenger_send(&start_messenger, new util::ptr<HTTPRequestBaton>(ptr));
+}
+
+// This function must be run from the main thread (== where the HTTPRequestBaton was created)
+void HTTPRequestBaton::stop(const util::ptr<HTTPRequestBaton> &ptr) {
+ assert(uv_thread_self() == ptr->thread_id);
+ uv_once(&once, create_thread);
+ uv_messenger_send(&stop_messenger, new util::ptr<HTTPRequestBaton>(ptr));
+}
+
+}
diff --git a/common/ios.mm b/common/ios.mm
new file mode 100644
index 0000000000..7989e73a4e
--- /dev/null
+++ b/common/ios.mm
@@ -0,0 +1,21 @@
+#import <Foundation/Foundation.h>
+
+#include <mbgl/platform/platform.hpp>
+
+namespace mbgl {
+namespace platform {
+
+// Returns the path to the default cache database on this system.
+std::string defaultCacheDatabase() {
+ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
+ if ([paths count] == 0) {
+ // Disable the cache if we don't have a location to write.
+ return "";
+ }
+
+ NSString *libraryDirectory = [paths objectAtIndex:0];
+ return [[libraryDirectory stringByAppendingPathComponent:@"cache.db"] UTF8String];
+}
+
+}
+}
diff --git a/common/linux.cpp b/common/linux.cpp
new file mode 100644
index 0000000000..6132ace692
--- /dev/null
+++ b/common/linux.cpp
@@ -0,0 +1,12 @@
+#include <mbgl/platform/platform.hpp>
+
+namespace mbgl {
+namespace platform {
+
+// Returns the path to the default cache database on this system.
+std::string defaultCacheDatabase() {
+ return "/tmp/mbgl-cache.db";
+}
+
+}
+}
diff --git a/common/osx.mm b/common/osx.mm
new file mode 100644
index 0000000000..974ea537cb
--- /dev/null
+++ b/common/osx.mm
@@ -0,0 +1,31 @@
+#import <Foundation/Foundation.h>
+
+#include <mbgl/platform/platform.hpp>
+
+namespace mbgl {
+namespace platform {
+
+// Returns the path to the default cache database on this system.
+std::string defaultCacheDatabase() {
+ NSArray *paths =
+ NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
+ if ([paths count] == 0) {
+ // Disable the cache if we don't have a location to write.
+ return "";
+ }
+
+ NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"Mapbox GL"];
+
+ if (![[NSFileManager defaultManager] createDirectoryAtPath:path
+ withIntermediateDirectories:YES
+ attributes:nil
+ error:nil]) {
+ // Disable the cache if we couldn't create the directory.
+ return "";
+ }
+
+ return [[path stringByAppendingPathComponent:@"cache.db"] UTF8String];
+}
+
+}
+}