summaryrefslogtreecommitdiff
path: root/platform
diff options
context:
space:
mode:
authorKonstantin Käfer <mail@kkaefer.com>2014-10-24 15:43:05 +0200
committerKonstantin Käfer <mail@kkaefer.com>2014-10-24 16:40:08 +0200
commitcd9a89257a0004ca18460befc4b141bc07ed5c22 (patch)
treee336a602b561854f043557096b83140aa04cd63a /platform
parent10c765948471e6d13e2eb45b44ac6c4d108dafc5 (diff)
downloadqtlocation-mapboxgl-cd9a89257a0004ca18460befc4b141bc07ed5c22.tar.gz
restructure gyp files
Diffstat (limited to 'platform')
-rw-r--r--platform/darwin/http_request_baton_cocoa.mm149
-rw-r--r--platform/darwin/log_nslog.mm39
-rw-r--r--platform/darwin/reachability.m527
-rw-r--r--platform/darwin/settings_nsuserdefaults.mm41
-rw-r--r--platform/darwin/string_nsstring.mm21
-rw-r--r--platform/default/cache_database_tmp.cpp12
-rw-r--r--platform/default/glfw_view.cpp303
-rw-r--r--platform/default/headless_view.cpp200
-rw-r--r--platform/default/http_request_baton_curl.cpp474
-rw-r--r--platform/default/log_stderr.cpp30
-rw-r--r--platform/default/settings_json.cpp46
-rw-r--r--platform/default/string_stdlib.cpp25
-rw-r--r--platform/ios/cache_database_library.mm21
-rw-r--r--platform/osx/cache_database_application_support.mm31
14 files changed, 1919 insertions, 0 deletions
diff --git a/platform/darwin/http_request_baton_cocoa.mm b/platform/darwin/http_request_baton_cocoa.mm
new file mode 100644
index 0000000000..a9992fad8c
--- /dev/null
+++ b/platform/darwin/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/platform/darwin/log_nslog.mm b/platform/darwin/log_nslog.mm
new file mode 100644
index 0000000000..231037b2dd
--- /dev/null
+++ b/platform/darwin/log_nslog.mm
@@ -0,0 +1,39 @@
+#include "log_nslog.hpp"
+
+#import <Foundation/Foundation.h>
+
+#include <cstdarg>
+
+namespace mbgl {
+
+void NSLogBackend::record(EventSeverity severity, Event event, const std::string &msg) {
+ NSLog(@"[%s] %s: %@", EventSeverityClass(severity).c_str(), EventClass(event).c_str(),
+ [[NSString alloc] initWithBytes:msg.data()
+ length:msg.size()
+ encoding:NSUTF8StringEncoding]);
+}
+
+
+void NSLogBackend::record(EventSeverity severity, Event event, const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ const size_t len = vsnprintf(NULL, 0, format, args);
+ va_end(args);
+ std::unique_ptr<char[]> buffer(new char[len + 1]);
+ va_start(args, format);
+ vsnprintf(buffer.get(), len + 1, format, args);
+ va_end(args);
+ NSLog(@"[%s] %s: %s", EventSeverityClass(severity).c_str(), EventClass(event).c_str(), buffer.get());
+}
+
+void NSLogBackend::record(EventSeverity severity, Event event, int64_t code) {
+ NSLog(@"[%s] %s: (%lld)", EventSeverityClass(severity).c_str(), EventClass(event).c_str(), code);
+}
+
+void NSLogBackend::record(EventSeverity severity, Event event, int64_t code, const std::string &msg) {
+ NSLog(@"[%s] %s: (%lld) %@", EventSeverityClass(severity).c_str(), EventClass(event).c_str(), code,
+ [[NSString alloc] initWithBytes:msg.data()
+ length:msg.size()
+ encoding:NSUTF8StringEncoding]);
+}
+}
diff --git a/platform/darwin/reachability.m b/platform/darwin/reachability.m
new file mode 100644
index 0000000000..8234659402
--- /dev/null
+++ b/platform/darwin/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 <mbgl/platform/darwin/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/platform/darwin/settings_nsuserdefaults.mm b/platform/darwin/settings_nsuserdefaults.mm
new file mode 100644
index 0000000000..b9b0e134bf
--- /dev/null
+++ b/platform/darwin/settings_nsuserdefaults.mm
@@ -0,0 +1,41 @@
+#import <Foundation/Foundation.h>
+
+#include <mbgl/platform/darwin/settings_nsuserdefaults.hpp>
+
+using namespace mbgl;
+
+Settings_NSUserDefaults::Settings_NSUserDefaults()
+{
+ [[NSUserDefaults standardUserDefaults] registerDefaults:@{ @"longitude" : @(longitude),
+ @"latitude" : @(latitude),
+ @"zoom" : @(zoom),
+ @"bearing" : @(bearing),
+ @"debug" : @(debug) }];
+ load();
+}
+
+void Settings_NSUserDefaults::load()
+{
+ NSDictionary *settings = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation];
+
+ longitude = [settings[@"longitude"] doubleValue];
+ latitude = [settings[@"latitude"] doubleValue];
+ zoom = [settings[@"zoom"] doubleValue];
+ bearing = [settings[@"bearing"] doubleValue];
+ debug = [settings[@"debug"] boolValue];
+}
+
+void Settings_NSUserDefaults::save()
+{
+ [[NSUserDefaults standardUserDefaults] setValuesForKeysWithDictionary:@{ @"longitude" : @(longitude),
+ @"latitude" : @(latitude),
+ @"zoom" : @(zoom),
+ @"bearing" : @(bearing),
+ @"debug" : @(debug) }];
+ [[NSUserDefaults standardUserDefaults] synchronize];
+}
+
+void Settings_NSUserDefaults::clear()
+{
+ [NSUserDefaults resetStandardUserDefaults];
+} \ No newline at end of file
diff --git a/platform/darwin/string_nsstring.mm b/platform/darwin/string_nsstring.mm
new file mode 100644
index 0000000000..9b1dc745cb
--- /dev/null
+++ b/platform/darwin/string_nsstring.mm
@@ -0,0 +1,21 @@
+#import <Foundation/Foundation.h>
+
+#include <mbgl/platform/platform.hpp>
+
+namespace mbgl {
+namespace platform {
+
+std::string uppercase(const std::string &string) {
+ NSString *nsstring = [[NSString alloc] initWithBytesNoCopy:const_cast<char *>(string.data()) length:string.size() encoding:NSUTF8StringEncoding freeWhenDone:NO];
+ nsstring = [nsstring uppercaseString];
+ return { [nsstring cStringUsingEncoding:NSUTF8StringEncoding], [nsstring lengthOfBytesUsingEncoding:NSUTF8StringEncoding] };
+}
+
+std::string lowercase(const std::string &string) {
+ NSString *nsstring = [[NSString alloc] initWithBytesNoCopy:const_cast<char *>(string.data()) length:string.size() encoding:NSUTF8StringEncoding freeWhenDone:NO];
+ nsstring = [nsstring lowercaseString];
+ return { [nsstring cStringUsingEncoding:NSUTF8StringEncoding], [nsstring lengthOfBytesUsingEncoding:NSUTF8StringEncoding] };
+}
+
+}
+}
diff --git a/platform/default/cache_database_tmp.cpp b/platform/default/cache_database_tmp.cpp
new file mode 100644
index 0000000000..6132ace692
--- /dev/null
+++ b/platform/default/cache_database_tmp.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/platform/default/glfw_view.cpp b/platform/default/glfw_view.cpp
new file mode 100644
index 0000000000..fed21f7d96
--- /dev/null
+++ b/platform/default/glfw_view.cpp
@@ -0,0 +1,303 @@
+#include <mbgl/platform/default/glfw_view.hpp>
+
+#include <mbgl/util/string.hpp>
+
+GLFWView::GLFWView(bool fullscreen) : fullscreen(fullscreen) {
+#ifdef NVIDIA
+ glDiscardFramebufferEXT = (PFNGLDISCARDFRAMEBUFFEREXTPROC)glfwGetProcAddress("glDiscardFramebufferEXT");
+#endif
+}
+
+GLFWView::~GLFWView() { glfwTerminate(); }
+
+void GLFWView::initialize(mbgl::Map *map) {
+ View::initialize(map);
+
+ if (!glfwInit()) {
+ fprintf(stderr, "Failed to initialize glfw\n");
+ exit(1);
+ }
+
+ GLFWmonitor *monitor = nullptr;
+ if (fullscreen) {
+ monitor = glfwGetPrimaryMonitor();
+ }
+
+#ifdef GL_ES_VERSION_2_0
+ glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
+#endif
+
+ glfwWindowHint(GLFW_RED_BITS, 8);
+ glfwWindowHint(GLFW_GREEN_BITS, 8);
+ glfwWindowHint(GLFW_BLUE_BITS, 8);
+ glfwWindowHint(GLFW_ALPHA_BITS, 8);
+ glfwWindowHint(GLFW_STENCIL_BITS, 8);
+ glfwWindowHint(GLFW_DEPTH_BITS, 16);
+
+ window = glfwCreateWindow(1024, 768, "Mapbox GL", monitor, NULL);
+ if (!window) {
+ glfwTerminate();
+ fprintf(stderr, "Failed to initialize window\n");
+ exit(1);
+ }
+
+ glfwSetWindowUserPointer(window, this);
+ glfwMakeContextCurrent(window);
+
+ int width, height;
+ glfwGetWindowSize(window, &width, &height);
+ int fb_width, fb_height;
+ glfwGetFramebufferSize(window, &fb_width, &fb_height);
+
+ resize(window, 0, 0);
+
+ glfwSetCursorPosCallback(window, mousemove);
+ glfwSetMouseButtonCallback(window, mouseclick);
+ glfwSetWindowSizeCallback(window, resize);
+ glfwSetFramebufferSizeCallback(window, resize);
+ glfwSetScrollCallback(window, scroll);
+ glfwSetKeyCallback(window, key);
+
+ glfwMakeContextCurrent(nullptr);
+}
+
+void GLFWView::key(GLFWwindow *window, int key, int /*scancode*/, int action, int mods) {
+ GLFWView *view = (GLFWView *)glfwGetWindowUserPointer(window);
+
+ if (action == GLFW_RELEASE) {
+ switch (key) {
+ case GLFW_KEY_ESCAPE:
+ glfwSetWindowShouldClose(window, true);
+ break;
+ case GLFW_KEY_TAB:
+ view->map->toggleDebug();
+ break;
+ case GLFW_KEY_X:
+ if (!mods)
+ view->map->resetPosition();
+ break;
+ case GLFW_KEY_R:
+ if (!mods) {
+ view->map->setDefaultTransitionDuration(300);
+ view->map->toggleClass("night");
+ }
+ break;
+ case GLFW_KEY_N:
+ if (!mods)
+ view->map->resetNorth();
+ break;
+ }
+ }
+}
+
+void GLFWView::scroll(GLFWwindow *window, double /*xoffset*/, double yoffset) {
+ GLFWView *view = (GLFWView *)glfwGetWindowUserPointer(window);
+ double delta = yoffset * 40;
+
+ bool is_wheel = delta != 0 && std::fmod(delta, 4.000244140625) == 0;
+
+ double absdelta = delta < 0 ? -delta : delta;
+ double scale = 2.0 / (1.0 + std::exp(-absdelta / 100.0));
+
+ // Make the scroll wheel a bit slower.
+ if (!is_wheel) {
+ scale = (scale - 1.0) / 2.0 + 1.0;
+ }
+
+ // Zooming out.
+ if (delta < 0 && scale != 0) {
+ scale = 1.0 / scale;
+ }
+
+ view->map->startScaling();
+ view->map->scaleBy(scale, view->last_x, view->last_y);
+}
+
+void GLFWView::resize(GLFWwindow *window, int, int) {
+ GLFWView *view = (GLFWView *)glfwGetWindowUserPointer(window);
+
+ int width, height;
+ glfwGetWindowSize(window, &width, &height);
+ int fb_width, fb_height;
+ glfwGetFramebufferSize(window, &fb_width, &fb_height);
+
+ view->map->resize(width, height, (float)fb_width / (float)width, fb_width, fb_height);
+}
+
+void GLFWView::mouseclick(GLFWwindow *window, int button, int action, int modifiers) {
+ GLFWView *view = (GLFWView *)glfwGetWindowUserPointer(window);
+
+ if (button == GLFW_MOUSE_BUTTON_RIGHT ||
+ (button == GLFW_MOUSE_BUTTON_LEFT && modifiers & GLFW_MOD_CONTROL)) {
+ view->rotating = action == GLFW_PRESS;
+ if (!view->rotating) {
+ view->map->stopRotating();
+ }
+ } else if (button == GLFW_MOUSE_BUTTON_LEFT) {
+ view->tracking = action == GLFW_PRESS;
+
+ if (action == GLFW_RELEASE) {
+ view->map->stopPanning();
+ double now = glfwGetTime();
+ if (now - view->last_click < 0.4 /* ms */) {
+ if (modifiers & GLFW_MOD_SHIFT) {
+ view->map->scaleBy(0.5, view->last_x, view->last_y, 0.5);
+ } else {
+ view->map->scaleBy(2.0, view->last_x, view->last_y, 0.5);
+ }
+ }
+ view->last_click = now;
+ }
+ }
+}
+
+void GLFWView::mousemove(GLFWwindow *window, double x, double y) {
+ GLFWView *view = (GLFWView *)glfwGetWindowUserPointer(window);
+ if (view->tracking) {
+ double dx = x - view->last_x;
+ double dy = y - view->last_y;
+ if (dx || dy) {
+ view->map->startPanning();
+ view->map->moveBy(dx, dy);
+ }
+ } else if (view->rotating) {
+ view->map->startRotating();
+ view->map->rotateBy(view->last_x, view->last_y, x, y);
+ }
+ view->last_x = x;
+ view->last_y = y;
+}
+
+int GLFWView::run() {
+ map->start();
+
+ while (!glfwWindowShouldClose(window)) {
+ if (map->needsSwap()) {
+ glfwSwapBuffers(window);
+ map->swapped();
+ fps();
+ }
+
+ glfwWaitEvents();
+ }
+
+ map->stop([](void *) {
+ glfwWaitEvents();
+ });
+
+ return 0;
+}
+
+void GLFWView::make_active() {
+ glfwMakeContextCurrent(window);
+}
+
+void GLFWView::notify() {
+ glfwPostEmptyEvent();
+}
+
+void GLFWView::swap() {
+ glfwPostEmptyEvent();
+
+ double lon, lat, zoom;
+ map->getLonLatZoom(lon, lat, zoom);
+ const double bearing = map->getBearing();
+ const std::string title = mbgl::util::sprintf<128>("Mapbox GL – %.2f/%.6f/%.6f/%.1f", zoom, lat, lon, bearing);
+ glfwSetWindowTitle(window, title.c_str());
+}
+
+void GLFWView::notify_map_change(mbgl::MapChange /*change*/, mbgl::timestamp /*delay*/) {
+ // no-op
+}
+
+void GLFWView::fps() {
+ static int frames = 0;
+ static double time_elapsed = 0;
+
+ frames++;
+ double current_time = glfwGetTime();
+
+ if (current_time - time_elapsed >= 1) {
+ fprintf(stderr, "FPS: %4.2f\n", frames / (current_time - time_elapsed));
+ time_elapsed = current_time;
+ frames = 0;
+ }
+}
+
+namespace mbgl {
+namespace platform {
+
+double elapsed() { return glfwGetTime(); }
+
+#ifndef GL_ES_VERSION_2_0
+void show_debug_image(std::string name, const char *data, size_t width, size_t height) {
+ glfwInit();
+
+ static GLFWwindow *debug_window = nullptr;
+ if (!debug_window) {
+ debug_window = glfwCreateWindow(width, height, name.c_str(), nullptr, nullptr);
+ if (!debug_window) {
+ glfwTerminate();
+ fprintf(stderr, "Failed to initialize window\n");
+ exit(1);
+ }
+ }
+
+ GLFWwindow *current_window = glfwGetCurrentContext();
+
+ glfwSetWindowSize(debug_window, width, height);
+ glfwMakeContextCurrent(debug_window);
+
+ int fb_width, fb_height;
+ glfwGetFramebufferSize(debug_window, &fb_width, &fb_height);
+ float scale = (float)fb_width / (float)width;
+
+ glPixelZoom(scale, -scale);
+ glRasterPos2f(-1.0f, 1.0f);
+ glDrawPixels(width, height, GL_LUMINANCE, GL_UNSIGNED_BYTE, data);
+ glfwSwapBuffers(debug_window);
+
+ glfwMakeContextCurrent(current_window);
+}
+
+
+void show_color_debug_image(std::string name, const char *data, size_t logical_width, size_t logical_height, size_t width, size_t height) {
+ glfwInit();
+
+ static GLFWwindow *debug_window = nullptr;
+ if (!debug_window) {
+ debug_window = glfwCreateWindow(logical_width, logical_height, name.c_str(), nullptr, nullptr);
+ if (!debug_window) {
+ glfwTerminate();
+ fprintf(stderr, "Failed to initialize window\n");
+ exit(1);
+ }
+ }
+
+ GLFWwindow *current_window = glfwGetCurrentContext();
+
+ glfwSetWindowSize(debug_window, logical_width, logical_height);
+ glfwMakeContextCurrent(debug_window);
+
+ int fb_width, fb_height;
+ glfwGetFramebufferSize(debug_window, &fb_width, &fb_height);
+ float x_scale = (float)fb_width / (float)width;
+ float y_scale = (float)fb_height / (float)height;
+
+ glClear(GL_COLOR_BUFFER_BIT);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ glPixelZoom(x_scale, -y_scale);
+ glRasterPos2f(-1.0f, 1.0f);
+ glDrawPixels(width, height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, data);
+ glfwSwapBuffers(debug_window);
+
+ glfwMakeContextCurrent(current_window);
+}
+#endif
+
+}
+}
diff --git a/platform/default/headless_view.cpp b/platform/default/headless_view.cpp
new file mode 100644
index 0000000000..ace41d38c0
--- /dev/null
+++ b/platform/default/headless_view.cpp
@@ -0,0 +1,200 @@
+#include "headless_view.hpp"
+#include <mbgl/util/timer.hpp>
+
+#include <stdexcept>
+
+namespace mbgl {
+
+HeadlessView::HeadlessView() {
+#if MBGL_USE_CGL
+ // TODO: test if OpenGL 4.1 with GL_ARB_ES2_compatibility is supported
+ // If it is, use kCGLOGLPVersion_3_2_Core and enable that extension.
+ CGLPixelFormatAttribute attributes[] = {
+ kCGLPFAOpenGLProfile,
+ (CGLPixelFormatAttribute) kCGLOGLPVersion_Legacy,
+ kCGLPFAAccelerated,
+ (CGLPixelFormatAttribute) 0
+ };
+
+ CGLPixelFormatObj pixelFormat;
+ GLint num;
+ CGLError error = CGLChoosePixelFormat(attributes, &pixelFormat, &num);
+ if (error) {
+ fprintf(stderr, "Error pixel format\n");
+ return;
+ }
+
+ error = CGLCreateContext(pixelFormat, NULL, &gl_context);
+ CGLDestroyPixelFormat(pixelFormat);
+ if (error) {
+ fprintf(stderr, "Error creating GL context object\n");
+ return;
+ }
+#endif
+
+#if MBGL_USE_GLX
+ x_display = XOpenDisplay(0);
+
+ if (x_display == nullptr) {
+ throw std::runtime_error("Failed to open X display");
+ }
+
+ static int pixelFormat[] = {
+ GLX_RGBA,
+ GLX_DOUBLEBUFFER,
+ GLX_RED_SIZE, 8,
+ GLX_GREEN_SIZE, 8,
+ GLX_BLUE_SIZE, 8,
+ GLX_ALPHA_SIZE, 8,
+ GLX_DEPTH_SIZE, 24,
+ GLX_STENCIL_SIZE, 8,
+ None
+ };
+
+ x_info = glXChooseVisual(x_display, DefaultScreen(x_display), pixelFormat);
+
+ if (x_info == nullptr) {
+ throw std::runtime_error("Error pixel format");
+ }
+
+ gl_context = glXCreateContext(x_display, x_info, 0, GL_TRUE);
+ if (gl_context == nullptr) {
+ throw std::runtime_error("Error creating GL context object");
+ }
+#endif
+}
+
+
+void HeadlessView::resize(uint16_t width, uint16_t height, float pixelRatio) {
+ clear_buffers();
+
+ width *= pixelRatio;
+ height *= pixelRatio;
+
+#if MBGL_USE_CGL
+ make_active();
+
+ // Create depth/stencil buffer
+ glGenRenderbuffersEXT(1, &fbo_depth_stencil);
+ glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fbo_depth_stencil);
+ glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH24_STENCIL8_EXT, width, height);
+ glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
+
+ glGenRenderbuffersEXT(1, &fbo_color);
+ glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fbo_color);
+ glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGBA8, width, height);
+ glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
+
+ glGenFramebuffersEXT(1, &fbo);
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
+
+ glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, fbo_color);
+ glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER_EXT, fbo_depth_stencil);
+
+ GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
+
+ if (status != GL_FRAMEBUFFER_COMPLETE_EXT) {
+ fprintf(stderr, "Couldn't create framebuffer: ");
+ switch (status) {
+ case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: fprintf(stderr, "incomplete attachment\n"); break;
+ case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: fprintf(stderr, "incomplete missing attachment\n"); break;
+ case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: fprintf(stderr, "incomplete draw buffer\n"); break;
+ case GL_FRAMEBUFFER_UNSUPPORTED: fprintf(stderr, "unsupported\n"); break;
+ default: fprintf(stderr, "other\n"); break;
+ }
+ return;
+ }
+#endif
+
+#if MBGL_USE_GLX
+ x_pixmap = XCreatePixmap(x_display, DefaultRootWindow(x_display), width, height, 32);
+ glx_pixmap = glXCreateGLXPixmap(x_display, x_info, x_pixmap);
+
+ make_active();
+#endif
+
+}
+
+void HeadlessView::clear_buffers() {
+#if MBGL_USE_CGL
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+
+ if (fbo) {
+ glDeleteFramebuffersEXT(1, &fbo);
+ fbo = 0;
+ }
+
+ if (fbo_color) {
+ glDeleteTextures(1, &fbo_color);
+ fbo_color = 0;
+ }
+
+ if (fbo_depth_stencil) {
+ glDeleteRenderbuffersEXT(1, &fbo_depth_stencil);
+ fbo_depth_stencil = 0;
+ }
+#endif
+
+#if MBGL_USE_GLX
+ if (glx_pixmap) {
+ glXDestroyGLXPixmap(x_display, glx_pixmap);
+ glx_pixmap = 0;
+ }
+
+ if (x_pixmap) {
+ XFreePixmap(x_display, x_pixmap);
+ x_pixmap = 0;
+ }
+#endif
+}
+
+HeadlessView::~HeadlessView() {
+ clear_buffers();
+
+#if MBGL_USE_CGL
+ CGLDestroyContext(gl_context);
+#endif
+
+#if MBGL_USE_GLX
+ glXMakeCurrent(x_display, None, NULL);
+ glXDestroyContext(x_display, gl_context);
+ XFree(x_info);
+ XCloseDisplay(x_display);
+#endif
+}
+
+void HeadlessView::notify() {
+ // no-op
+}
+
+void HeadlessView::notify_map_change(mbgl::MapChange /*change*/, mbgl::timestamp /*delay*/) {
+ // no-op
+}
+
+void HeadlessView::make_active() {
+#if MBGL_USE_CGL
+ CGLError error = CGLSetCurrentContext(gl_context);
+ if (error) {
+ fprintf(stderr, "Switching OpenGL context failed\n");
+ }
+#endif
+
+#if MBGL_USE_GLX
+ if (!glXMakeCurrent(x_display, glx_pixmap, gl_context)) {
+ fprintf(stderr, "Switching OpenGL context failed\n");
+ }
+#endif
+}
+
+void HeadlessView::swap() {}
+
+unsigned int HeadlessView::root_fbo() {
+#if MBGL_USE_CGL
+ return fbo;
+#endif
+
+ return 0;
+}
+
+}
+
diff --git a/platform/default/http_request_baton_curl.cpp b/platform/default/http_request_baton_curl.cpp
new file mode 100644
index 0000000000..d4753266c0
--- /dev/null
+++ b/platform/default/http_request_baton_curl.cpp
@@ -0,0 +1,474 @@
+#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 = nullptr;
+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;
+}
+
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+void on_timeout(uv_timer_t *, int = 0) {
+#else
+void on_timeout(uv_timer_t *) {
+#endif
+ 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);
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+ loop = uv_loop_new();
+#else
+ loop = new uv_loop_t;
+ uv_loop_init(loop);
+#endif
+ 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/platform/default/log_stderr.cpp b/platform/default/log_stderr.cpp
new file mode 100644
index 0000000000..62938b6d63
--- /dev/null
+++ b/platform/default/log_stderr.cpp
@@ -0,0 +1,30 @@
+#include <mbgl/platform/default/log_stderr.hpp>
+
+#include <iostream>
+#include <cstdarg>
+
+namespace mbgl {
+
+void StderrLogBackend::record(EventSeverity severity, Event event, const std::string &msg) {
+ std::cerr << "[" << severity << "] " << event << ": " << msg << std::endl;
+}
+
+void StderrLogBackend::record(EventSeverity severity, Event event, const char* format, ...) {
+ std::cerr << "[" << severity << "] " << event << ": ";
+ va_list args;
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+ std::cerr << std::endl;
+}
+
+void StderrLogBackend::record(EventSeverity severity, Event event, int64_t code) {
+ std::cerr << "[" << severity << "] " << event << ": (" << code << ")" << std::endl;
+}
+
+void StderrLogBackend::record(EventSeverity severity, Event event, int64_t code, const std::string &msg) {
+ std::cerr << "[" << severity << "] " << event << ": (" << code << ") " << msg << std::endl;
+
+}
+
+}
diff --git a/platform/default/settings_json.cpp b/platform/default/settings_json.cpp
new file mode 100644
index 0000000000..f81c75115e
--- /dev/null
+++ b/platform/default/settings_json.cpp
@@ -0,0 +1,46 @@
+#include <mbgl/platform/default/settings_json.hpp>
+#include <rapidjson/prettywriter.h>
+#include <rapidjson/filestream.h>
+#include <rapidjson/document.h>
+#include <stdio.h>
+
+using namespace mbgl;
+
+Settings_JSON::Settings_JSON() { load(); }
+
+void Settings_JSON::load() {
+ FILE *settingsFile = fopen("/tmp/mbgl-native.json", "r");
+ if (settingsFile != NULL) {
+ rapidjson::FileStream is(settingsFile);
+ rapidjson::Document document;
+ document.ParseStream<0>(is);
+ if (document.IsArray()) {
+ longitude = document[rapidjson::SizeType(0)].GetDouble();
+ latitude = document[1].GetDouble();
+ zoom = document[2].GetDouble();
+ bearing = document[3].GetDouble();
+ debug = document[4].GetBool();
+ }
+ }
+}
+
+void Settings_JSON::save() {
+
+ rapidjson::FileStream s(fopen("/tmp/mbgl-native.json", "w"));
+ rapidjson::PrettyWriter<rapidjson::FileStream> writer(s);
+ writer.StartArray();
+ writer.Double(longitude);
+ writer.Double(latitude);
+ writer.Double(zoom);
+ writer.Double(bearing);
+ writer.Bool(debug);
+ writer.EndArray();
+}
+
+void Settings_JSON::clear() {
+ longitude = 0;
+ latitude = 0;
+ zoom = 0;
+ bearing = 0;
+ debug = false;
+}
diff --git a/platform/default/string_stdlib.cpp b/platform/default/string_stdlib.cpp
new file mode 100644
index 0000000000..1acbfa8508
--- /dev/null
+++ b/platform/default/string_stdlib.cpp
@@ -0,0 +1,25 @@
+#include <mbgl/platform/platform.hpp>
+
+#include <locale>
+
+namespace mbgl {
+namespace platform {
+
+std::string uppercase(const std::string& string) {
+ // TODO: Use a proper Unicode Special Casing-aware algorithm.
+ const auto &convert = std::use_facet<std::ctype<char>>(std::locale());
+ std::string converted = string;
+ convert.toupper(&converted[0], &converted[0] + converted.size());
+ return converted;
+}
+
+std::string lowercase(const std::string& string) {
+ // TODO: Use a proper Unicode Special Casing-aware algorithm.
+ const auto &convert = std::use_facet<std::ctype<char>>(std::locale());
+ std::string converted = string;
+ convert.tolower(&converted[0], &converted[0] + converted.size());
+ return converted;
+}
+
+}
+}
diff --git a/platform/ios/cache_database_library.mm b/platform/ios/cache_database_library.mm
new file mode 100644
index 0000000000..7989e73a4e
--- /dev/null
+++ b/platform/ios/cache_database_library.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/platform/osx/cache_database_application_support.mm b/platform/osx/cache_database_application_support.mm
new file mode 100644
index 0000000000..974ea537cb
--- /dev/null
+++ b/platform/osx/cache_database_application_support.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];
+}
+
+}
+}