diff options
author | Konstantin Käfer <mail@kkaefer.com> | 2014-10-24 15:43:05 +0200 |
---|---|---|
committer | Konstantin Käfer <mail@kkaefer.com> | 2014-10-24 16:40:08 +0200 |
commit | cd9a89257a0004ca18460befc4b141bc07ed5c22 (patch) | |
tree | e336a602b561854f043557096b83140aa04cd63a /platform | |
parent | 10c765948471e6d13e2eb45b44ac6c4d108dafc5 (diff) | |
download | qtlocation-mapboxgl-cd9a89257a0004ca18460befc4b141bc07ed5c22.tar.gz |
restructure gyp files
Diffstat (limited to 'platform')
-rw-r--r-- | platform/darwin/http_request_baton_cocoa.mm | 149 | ||||
-rw-r--r-- | platform/darwin/log_nslog.mm | 39 | ||||
-rw-r--r-- | platform/darwin/reachability.m | 527 | ||||
-rw-r--r-- | platform/darwin/settings_nsuserdefaults.mm | 41 | ||||
-rw-r--r-- | platform/darwin/string_nsstring.mm | 21 | ||||
-rw-r--r-- | platform/default/cache_database_tmp.cpp | 12 | ||||
-rw-r--r-- | platform/default/glfw_view.cpp | 303 | ||||
-rw-r--r-- | platform/default/headless_view.cpp | 200 | ||||
-rw-r--r-- | platform/default/http_request_baton_curl.cpp | 474 | ||||
-rw-r--r-- | platform/default/log_stderr.cpp | 30 | ||||
-rw-r--r-- | platform/default/settings_json.cpp | 46 | ||||
-rw-r--r-- | platform/default/string_stdlib.cpp | 25 | ||||
-rw-r--r-- | platform/ios/cache_database_library.mm | 21 | ||||
-rw-r--r-- | platform/osx/cache_database_application_support.mm | 31 |
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]; +} + +} +} |