summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKonstantin Käfer <mail@kkaefer.com>2014-09-15 17:26:44 +0200
committerKonstantin Käfer <mail@kkaefer.com>2014-09-24 16:14:09 +0200
commitd9fc7708a2dfb6e2506a5d10d896a813557c056d (patch)
tree50b2dba9e0a8766c88f7c276a8f71742a06a1d67
parent062e911c6d570a794431023f9f0cb0b02cd85667 (diff)
downloadqtlocation-mapboxgl-d9fc7708a2dfb6e2506a5d10d896a813557c056d.tar.gz
do 304 requests and cache them in sqlite
-rw-r--r--common/foundation_request.h1
-rw-r--r--common/foundation_request.mm119
-rw-r--r--include/mbgl/map/map.hpp8
-rw-r--r--include/mbgl/map/tile_data.hpp5
-rw-r--r--include/mbgl/platform/platform.hpp13
-rw-r--r--include/mbgl/platform/request.hpp41
-rw-r--r--include/mbgl/platform/response.hpp29
-rw-r--r--include/mbgl/storage/base_request.hpp46
-rw-r--r--include/mbgl/storage/file_request.hpp32
-rw-r--r--include/mbgl/storage/file_source.hpp54
-rw-r--r--include/mbgl/storage/http_request.hpp38
-rw-r--r--include/mbgl/storage/http_request_baton.hpp28
-rw-r--r--include/mbgl/storage/request.hpp39
-rw-r--r--include/mbgl/storage/resource_type.hpp18
-rw-r--r--include/mbgl/storage/response.hpp23
-rw-r--r--include/mbgl/storage/sqlite_store.hpp47
-rw-r--r--include/mbgl/text/glyph_store.hpp7
-rw-r--r--include/mbgl/util/filesource.hpp56
-rw-r--r--include/mbgl/util/interpolate.hpp2
-rw-r--r--include/mbgl/util/parsedate.h38
-rw-r--r--include/mbgl/util/queue.h92
-rw-r--r--include/mbgl/util/sqlite3.hpp74
-rw-r--r--include/mbgl/util/uv-channel.h29
-rw-r--r--include/mbgl/util/uv-messenger.h31
-rw-r--r--include/mbgl/util/uv-worker.h36
-rw-r--r--macosx/main.mm3
-rw-r--r--macosx/mapboxgl-app.gyp3
-rw-r--r--mapboxgl.gyp8
-rw-r--r--src/map/map.cpp58
-rw-r--r--src/map/source.cpp9
-rw-r--r--src/map/sprite.cpp22
-rw-r--r--src/map/tile_data.cpp19
-rw-r--r--src/map/vector_tile_data.cpp16
-rw-r--r--src/platform/request.cpp54
-rw-r--r--src/platform/response.cpp31
-rw-r--r--src/storage/base_request.cpp66
-rw-r--r--src/storage/file_request.cpp179
-rw-r--r--src/storage/file_source.cpp80
-rw-r--r--src/storage/http_request.cpp112
-rw-r--r--src/storage/http_request_baton_darwin.mm103
-rw-r--r--src/storage/request.cpp39
-rw-r--r--src/storage/response.cpp22
-rw-r--r--src/storage/sqlite_store.cpp194
-rw-r--r--src/text/glyph_store.cpp35
-rw-r--r--src/util/filesource.cpp296
-rw-r--r--src/util/parsedate.c689
-rw-r--r--src/util/sqlite3.cpp166
-rw-r--r--src/util/uv-channel.c55
-rw-r--r--src/util/uv-messenger.c70
-rw-r--r--src/util/uv-worker.c62
50 files changed, 2581 insertions, 716 deletions
diff --git a/common/foundation_request.h b/common/foundation_request.h
deleted file mode 100644
index 0b8c4b8fb0..0000000000
--- a/common/foundation_request.h
+++ /dev/null
@@ -1 +0,0 @@
-#import <Foundation/Foundation.h>
diff --git a/common/foundation_request.mm b/common/foundation_request.mm
deleted file mode 100644
index 478dadf4d9..0000000000
--- a/common/foundation_request.mm
+++ /dev/null
@@ -1,119 +0,0 @@
-#import "foundation_request.h"
-
-#include "TargetConditionals.h"
-#if TARGET_OS_IPHONE
-#import <UIKit/UIKit.h>
-#include <atomic>
-#endif
-
-#include <memory>
-#include <string>
-#include <functional>
-#include <mbgl/platform/request.hpp>
-#include <mbgl/platform/platform.hpp>
-#include <mbgl/util/std.hpp>
-#include <mbgl/util/uv.hpp>
-
-dispatch_once_t request_initialize = 0;
-NSURLSession *session = nullptr;
-
-#if TARGET_OS_IPHONE
-std::atomic<int> active_tasks;
-#endif
-
-
-// We're using a child class to make sure ARC is working correctly, as well as to add activity
-// indicators on iOS.
-class FoundationRequest : public mbgl::platform::Request {
-public:
- FoundationRequest(const std::string &url,
- std::function<void(mbgl::platform::Response *)> callback,
- std::shared_ptr<uv::loop> loop)
- : Request(url, callback, loop) {
-#if TARGET_OS_IPHONE
- active_tasks++;
- dispatch_async(dispatch_get_main_queue(), ^(void) {
- [[UIApplication sharedApplication]
- setNetworkActivityIndicatorVisible:(active_tasks > 0)];
- });
-#endif
- }
-
- ~FoundationRequest() {
-#if TARGET_OS_IPHONE
- active_tasks--;
- dispatch_async(dispatch_get_main_queue(), ^(void) {
- [[UIApplication sharedApplication]
- setNetworkActivityIndicatorVisible:(active_tasks > 0)];
- });
-#endif
- }
-
- NSURLSessionDataTask *task = nullptr;
-};
-
-std::shared_ptr<mbgl::platform::Request>
-mbgl::platform::request_http(const std::string &url,
- std::function<void(Response *)> callback,
- std::shared_ptr<uv::loop> loop) {
- dispatch_once(&request_initialize, ^{
- NSURLSessionConfiguration *sessionConfig =
- [NSURLSessionConfiguration defaultSessionConfiguration];
- sessionConfig.timeoutIntervalForResource = 30;
- sessionConfig.HTTPMaximumConnectionsPerHost = 8;
- sessionConfig.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
-
- session = [NSURLSession sessionWithConfiguration:sessionConfig];
-
-#if TARGET_OS_IPHONE
- active_tasks = 0;
-#endif
- });
-
- std::shared_ptr<FoundationRequest> req =
- std::make_shared<FoundationRequest>(url, callback, loop);
-
- // Note that we are creating a new shared_ptr pointer(!) to make sure there is at least one
- // shared_ptr in existence while the NSURLSession is loading our data. We are making sure in the
- // callback that this pointer gets destroyed again.
- std::shared_ptr<Request> *req_ptr = new std::shared_ptr<Request>(req);
-
- NSURLSessionDataTask *task = [session
- dataTaskWithURL:[NSURL URLWithString:@(url.c_str())]
- completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
- if ([error code] == NSURLErrorCancelled) {
- // We intentionally cancelled this request. Make sure we clear the shared_ptr to resolve
- // the circular reference. We're referencing this shared_ptr by value so that the object
- // stays around until this completion handler is invoked.
- delete req_ptr;
-
- return;
- }
-
- if (!error && [response isKindOfClass:[NSHTTPURLResponse class]]) {
- NSDictionary *headers = [(NSHTTPURLResponse *)response allHeaderFields];
- (*req_ptr)->res->code = [(NSHTTPURLResponse *)response statusCode];
- (*req_ptr)->res->body = {(const char *)[data bytes], [data length]};
- (*req_ptr)->res->setCacheControl([[headers objectForKey:@"Cache-Control"] UTF8String]);
- (*req_ptr)->res->setLastModified([[headers objectForKey:@"Last-Modified"] UTF8String]);
-
- } else {
- (*req_ptr)->res->error_message = [[error localizedDescription] UTF8String];
- }
-
- (*req_ptr)->complete();
-
- delete req_ptr;
- }];
-
- req->task = task;
-
- [task resume];
- return req;
-}
-
-void mbgl::platform::cancel_request_http(const std::shared_ptr<Request> &req) {
- if (req) {
- [((FoundationRequest *)(req.get()))->task cancel];
- }
-}
diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp
index fba38879ed..98951a375c 100644
--- a/include/mbgl/map/map.hpp
+++ b/include/mbgl/map/map.hpp
@@ -35,11 +35,14 @@ public:
explicit Map(View &view);
~Map();
- // Start/stop the map render thread
+ // Start/stop the map render thread. the start() call is asynchronous, while the stop() call
+ // will block until the map rendering thread stopped.
void start();
void stop();
- // Runs the map event loop.
+ // Runs the map event loop. ONLY run this function when you want to get render a single frame
+ // with this map object. It will *not* spawn a separate thread and instead block until the
+ // frame is completely rendered.
void run();
// Triggers a lazy rerender: only performs a render when the map is not clean.
@@ -192,6 +195,7 @@ private:
Painter painter;
+ std::string styleURL;
std::string styleJSON = "";
std::string accessToken = "";
diff --git a/include/mbgl/map/tile_data.hpp b/include/mbgl/map/tile_data.hpp
index 9aaef84e04..900224be2d 100644
--- a/include/mbgl/map/tile_data.hpp
+++ b/include/mbgl/map/tile_data.hpp
@@ -19,8 +19,7 @@ class Map;
class Painter;
class SourceInfo;
class StyleLayer;
-
-namespace platform { class Request; }
+class Request;
class TileData : public std::enable_shared_from_this<TileData>,
private util::noncopyable {
@@ -72,7 +71,7 @@ public:
const SourceInfo &source;
protected:
- std::weak_ptr<platform::Request> req;
+ std::unique_ptr<Request> req;
std::string data;
// Contains the tile ID string for painting debug information.
diff --git a/include/mbgl/platform/platform.hpp b/include/mbgl/platform/platform.hpp
index e08cac5312..6356d81bb1 100644
--- a/include/mbgl/platform/platform.hpp
+++ b/include/mbgl/platform/platform.hpp
@@ -1,8 +1,6 @@
#ifndef MBGL_PLATFORM_PLATFORM
#define MBGL_PLATFORM_PLATFORM
-#include <mbgl/platform/response.hpp>
-
#include <mbgl/util/uv.hpp>
#include <memory>
@@ -13,23 +11,12 @@ namespace platform {
class Request;
-// Makes an HTTP request of a URL, preferrably on a background thread, and calls a function with the
-// results in the original thread (which runs the libuv loop).
-// If the loop pointer is NULL, the callback function will be called on an arbitrary thread.
-// Returns a cancellable request.
-std::shared_ptr<Request> request_http(const std::string &url,
- std::function<void(Response *)> callback,
- std::shared_ptr<uv::loop> loop = nullptr);
-
// Uppercase a string, potentially using platform-specific routines.
std::string uppercase(const std::string &string);
// Lowercase a string, potentially using platform-specific routines.
std::string lowercase(const std::string &string);
-// Cancels an HTTP request.
-void cancel_request_http(const std::shared_ptr<Request> &req);
-
// Shows an alpha image with the specified dimensions in a named window.
void show_debug_image(std::string name, const char *data, size_t width, size_t height);
diff --git a/include/mbgl/platform/request.hpp b/include/mbgl/platform/request.hpp
deleted file mode 100644
index 0cbacf645d..0000000000
--- a/include/mbgl/platform/request.hpp
+++ /dev/null
@@ -1,41 +0,0 @@
-#ifndef MBGL_PLATFORM_REQUEST
-#define MBGL_PLATFORM_REQUEST
-
-#include <string>
-#include <functional>
-#include <memory>
-#include <atomic>
-
-#include <mbgl/util/noncopyable.hpp>
-#include <mbgl/util/uv.hpp>
-
-namespace mbgl {
-namespace platform {
-
-struct Response;
-
-class Request : public std::enable_shared_from_this<Request>, private util::noncopyable {
-public:
- Request(const std::string &url,
- std::function<void(Response *)> callback,
- std::shared_ptr<uv::loop> loop);
- ~Request();
-
- void complete();
-
-private:
- static void complete(uv_async_t *async);
-
-public:
- const std::string url;
- std::unique_ptr<Response> res;
- std::atomic<bool> cancelled;
-
-public:
- uv_async_t *async = nullptr;
- std::shared_ptr<uv::loop> loop;
-};
-}
-}
-
-#endif
diff --git a/include/mbgl/platform/response.hpp b/include/mbgl/platform/response.hpp
deleted file mode 100644
index 345a2ee3df..0000000000
--- a/include/mbgl/platform/response.hpp
+++ /dev/null
@@ -1,29 +0,0 @@
-#ifndef MBGL_PLATFORM_RESPONSE
-#define MBGL_PLATFORM_RESPONSE
-
-#include <string>
-#include <functional>
-#include <ctime>
-
-#include <mbgl/util/noncopyable.hpp>
-
-namespace mbgl {
-namespace platform {
-
-struct Response : private util::noncopyable {
- Response(std::function<void(Response *)> callback) : callback(callback) {}
- int16_t code = -1;
- std::string body;
- std::string error_message;
- std::time_t modified;
- std::time_t expires;
- std::function<void(Response *)> callback;
-
- void setCacheControl(const char *value);
- void setLastModified(const char *value);
-};
-
-}
-}
-
-#endif
diff --git a/include/mbgl/storage/base_request.hpp b/include/mbgl/storage/base_request.hpp
new file mode 100644
index 0000000000..b8ebd368e4
--- /dev/null
+++ b/include/mbgl/storage/base_request.hpp
@@ -0,0 +1,46 @@
+#ifndef MBGL_STORAGE_BASE_REQUEST
+#define MBGL_STORAGE_BASE_REQUEST
+
+#include <string>
+#include <forward_list>
+#include <memory>
+#include <functional>
+
+typedef struct uv_loop_s uv_loop_t;
+typedef struct uv_async_s uv_async_t;
+
+namespace mbgl {
+
+class Response;
+class Request;
+using Callback = std::function<void(const Response &)>;
+
+
+class BaseRequest {
+private:
+ // Make noncopyable and immovable
+ BaseRequest(const BaseRequest &) = delete;
+ BaseRequest(BaseRequest &&) = delete;
+ BaseRequest& operator=(const BaseRequest &) = delete;
+ BaseRequest& operator=(BaseRequest &&) = delete;
+
+public:
+ BaseRequest();
+ virtual ~BaseRequest();
+
+ Callback *add(Callback &&callback, const std::shared_ptr<BaseRequest> &request);
+ void remove(Callback *callback);
+ void notify();
+
+public:
+ const unsigned long thread_id;
+ std::unique_ptr<Response> response;
+
+private:
+ std::shared_ptr<BaseRequest> self;
+ std::forward_list<std::unique_ptr<Callback>> callbacks;
+};
+
+}
+
+#endif
diff --git a/include/mbgl/storage/file_request.hpp b/include/mbgl/storage/file_request.hpp
new file mode 100644
index 0000000000..156fd6dfe7
--- /dev/null
+++ b/include/mbgl/storage/file_request.hpp
@@ -0,0 +1,32 @@
+#ifndef MBGL_STORAGE_FILE_REQUEST
+#define MBGL_STORAGE_FILE_REQUEST
+
+
+#include <string>
+#include <memory>
+#include <cassert>
+
+#include <mbgl/storage/base_request.hpp>
+
+namespace mbgl {
+
+typedef struct uv_loop_s uv_loop_t;
+
+struct FileRequestBaton;
+
+class FileRequest : public BaseRequest {
+public:
+ FileRequest(const std::string &path, uv_loop_t *loop);
+ ~FileRequest();
+
+private:
+ const std::string path;
+ const unsigned long thread_id;
+ FileRequestBaton *ptr = nullptr;
+
+ friend struct FileRequestBaton;
+};
+
+}
+
+#endif \ No newline at end of file
diff --git a/include/mbgl/storage/file_source.hpp b/include/mbgl/storage/file_source.hpp
new file mode 100644
index 0000000000..4cc95ae24e
--- /dev/null
+++ b/include/mbgl/storage/file_source.hpp
@@ -0,0 +1,54 @@
+#ifndef MBGL_STORAGE_FILE_SOURCE
+#define MBGL_STORAGE_FILE_SOURCE
+
+#include <mbgl/storage/resource_type.hpp>
+#include <mbgl/storage/request.hpp>
+
+#include <string>
+#include <memory>
+#include <unordered_map>
+#include <functional>
+
+typedef struct uv_loop_s uv_loop_t;
+typedef struct uv_messenger_s uv_messenger_t;
+
+namespace mbgl {
+
+class BaseRequest;
+class SQLiteStore;
+
+class FileSource {
+private:
+ FileSource(const FileSource &) = delete;
+ FileSource(FileSource &&) = delete;
+ FileSource& operator=(const FileSource &) = delete;
+ FileSource& operator=(FileSource &&) = delete;
+
+public:
+ FileSource(uv_loop_t *loop);
+ ~FileSource();
+
+public:
+ // Stores and retrieves the base path/URL for relative requests
+ void setBase(const std::string &value);
+ const std::string &getBase() const;
+
+ std::unique_ptr<Request> request(ResourceType type, const std::string &url);
+
+ void prepare(std::function<void()> fn);
+
+private:
+ const unsigned long thread_id;
+
+ // Stores a URL that is used as a base for loading resources with relative path.
+ std::string base;
+
+ std::unordered_map<std::string, std::weak_ptr<BaseRequest>> pending;
+ std::shared_ptr<SQLiteStore> store;
+ uv_loop_t *loop = nullptr;
+ uv_messenger_t *queue = nullptr;
+};
+
+}
+
+#endif
diff --git a/include/mbgl/storage/http_request.hpp b/include/mbgl/storage/http_request.hpp
new file mode 100644
index 0000000000..30f7b3aa6d
--- /dev/null
+++ b/include/mbgl/storage/http_request.hpp
@@ -0,0 +1,38 @@
+#ifndef MBGL_STORAGE_HTTP_REQUEST
+#define MBGL_STORAGE_HTTP_REQUEST
+
+#include <mbgl/storage/resource_type.hpp>
+#include <mbgl/storage/base_request.hpp>
+
+#include <string>
+#include <memory>
+#include <cassert>
+
+typedef struct uv_loop_s uv_loop_t;
+
+namespace mbgl {
+
+struct CacheRequestBaton;
+struct HTTPRequestBaton;
+struct CacheEntry;
+class SQLiteStore;
+
+class HTTPRequest : public BaseRequest {
+public:
+ HTTPRequest(ResourceType type, const std::string &path, uv_loop_t *loop, std::shared_ptr<SQLiteStore> store);
+ ~HTTPRequest();
+
+private:
+ void loadedCacheEntry(std::unique_ptr<Response> &&response);
+
+private:
+ const unsigned long thread_id;
+ CacheRequestBaton *cache_baton = nullptr;
+ HTTPRequestBaton *http_baton = nullptr;
+ std::shared_ptr<SQLiteStore> store;
+ const ResourceType type;
+};
+
+}
+
+#endif \ No newline at end of file
diff --git a/include/mbgl/storage/http_request_baton.hpp b/include/mbgl/storage/http_request_baton.hpp
new file mode 100644
index 0000000000..5f06a68cd1
--- /dev/null
+++ b/include/mbgl/storage/http_request_baton.hpp
@@ -0,0 +1,28 @@
+#ifndef MBGL_STORAGE_HTTP_REQUEST_BATON
+#define MBGL_STORAGE_HTTP_REQUEST_BATON
+
+#include <mbgl/storage/response.hpp>
+
+#include <string>
+
+typedef struct uv_async_s uv_async_t;
+
+namespace mbgl {
+
+class HTTPRequest;
+
+struct HTTPRequestBaton {
+ HTTPRequest *request = nullptr;
+ std::string path;
+ uv_async_t *async = nullptr;
+ std::unique_ptr<Response> response;
+ void *ptr = nullptr;
+ bool not_modified = false;
+
+ void start();
+ void cancel();
+};
+
+}
+
+#endif
diff --git a/include/mbgl/storage/request.hpp b/include/mbgl/storage/request.hpp
new file mode 100644
index 0000000000..fa27fbc781
--- /dev/null
+++ b/include/mbgl/storage/request.hpp
@@ -0,0 +1,39 @@
+#ifndef MBGL_STORAGE_REQUEST
+#define MBGL_STORAGE_REQUEST
+
+#include <mbgl/storage/response.hpp>
+
+#include <memory>
+#include <functional>
+#include <forward_list>
+
+typedef struct uv_loop_s uv_loop_t;
+
+namespace mbgl {
+
+class BaseRequest;
+using Callback = std::function<void(const Response &)>;
+
+class Request {
+private:
+ Request(const Request &) = delete;
+ Request(Request &&) = delete;
+ Request& operator=(const Request &) = delete;
+ Request& operator=(Request &&) = delete;
+
+public:
+ Request(const std::shared_ptr<BaseRequest> &base);
+ ~Request();
+
+ void onload(Callback cb);
+ void cancel();
+
+private:
+ const unsigned long thread_id;
+ std::shared_ptr<BaseRequest> base;
+ std::forward_list<Callback *> callbacks;
+};
+
+}
+
+#endif \ No newline at end of file
diff --git a/include/mbgl/storage/resource_type.hpp b/include/mbgl/storage/resource_type.hpp
new file mode 100644
index 0000000000..b7204a9fa1
--- /dev/null
+++ b/include/mbgl/storage/resource_type.hpp
@@ -0,0 +1,18 @@
+#ifndef MBGL_STORAGE_RESOURCE_TYPE
+#define MBGL_STORAGE_RESOURCE_TYPE
+
+#include <cstdint>
+
+namespace mbgl {
+
+enum class ResourceType : uint8_t {
+ Unknown = 0,
+ Tile = 1,
+ Glyphs = 2,
+ Image = 3,
+ JSON = 4
+};
+
+}
+
+#endif
diff --git a/include/mbgl/storage/response.hpp b/include/mbgl/storage/response.hpp
new file mode 100644
index 0000000000..4960173f9e
--- /dev/null
+++ b/include/mbgl/storage/response.hpp
@@ -0,0 +1,23 @@
+#ifndef MBGL_STORAGE_RESPONSE
+#define MBGL_STORAGE_RESPONSE
+
+#include <string>
+#include <ctime>
+
+namespace mbgl {
+
+class Response {
+public:
+ long code = 0;
+ int64_t modified = 0;
+ int64_t expires = 0;
+ std::string data;
+
+ std::string message;
+
+ static int64_t parseCacheControl(const char *value);
+};
+
+}
+
+#endif \ No newline at end of file
diff --git a/include/mbgl/storage/sqlite_store.hpp b/include/mbgl/storage/sqlite_store.hpp
new file mode 100644
index 0000000000..e03e6cf2bc
--- /dev/null
+++ b/include/mbgl/storage/sqlite_store.hpp
@@ -0,0 +1,47 @@
+#ifndef MBGL_STORAGE_SQLITE_STORE
+#define MBGL_STORAGE_SQLITE_STORE
+
+#include <mbgl/storage/file_source.hpp>
+#include <mbgl/storage/response.hpp>
+
+#include <uv.h>
+
+#include <string>
+
+typedef struct uv_worker_s uv_worker_t;
+
+namespace mapbox {
+namespace sqlite {
+class Database;
+}
+}
+
+namespace mbgl {
+
+class SQLiteStore {
+public:
+ SQLiteStore(uv_loop_t *loop, const std::string &path);
+ ~SQLiteStore();
+
+ typedef void (*GetCallback)(std::unique_ptr<Response> &&entry, void *ptr);
+
+ void get(const std::string &path, GetCallback cb, void *ptr);
+ void put(const std::string &path, ResourceType type, const Response &entry);
+ void updateExpiration(const std::string &path, int64_t expires);
+
+private:
+ void createSchema();
+ void closeDatabase();
+ static void runGet(uv_work_t *req);
+ static void runPut(uv_work_t *req);
+ static void deliverResult(uv_work_t *req, int status);
+
+private:
+ const unsigned long thread_id;
+ std::shared_ptr<mapbox::sqlite::Database> db;
+ uv_worker_t *worker = nullptr;
+};
+
+}
+
+#endif
diff --git a/include/mbgl/text/glyph_store.hpp b/include/mbgl/text/glyph_store.hpp
index e0c0391c73..9730967053 100644
--- a/include/mbgl/text/glyph_store.hpp
+++ b/include/mbgl/text/glyph_store.hpp
@@ -49,6 +49,13 @@ class GlyphPBF {
public:
GlyphPBF(const std::string &glyphURL, const std::string &fontStack, GlyphRange glyphRange, const std::shared_ptr<FileSource> &fileSource);
+private:
+ GlyphPBF(const GlyphPBF &) = delete;
+ GlyphPBF(GlyphPBF &&) = delete;
+ GlyphPBF &operator=(const GlyphPBF &) = delete;
+ GlyphPBF &operator=(GlyphPBF &&) = delete;
+
+public:
void parse(FontStack &stack);
std::shared_future<GlyphPBF &> getFuture();
diff --git a/include/mbgl/util/filesource.hpp b/include/mbgl/util/filesource.hpp
deleted file mode 100644
index ccff4b5122..0000000000
--- a/include/mbgl/util/filesource.hpp
+++ /dev/null
@@ -1,56 +0,0 @@
-#ifndef MBGL_UTIL_FILESOURCE
-#define MBGL_UTIL_FILESOURCE
-
-#include <mbgl/util/uv.hpp>
-#include <mbgl/util/noncopyable.hpp>
-
-#include <string>
-#include <memory>
-#include <functional>
-
-typedef struct sqlite3 sqlite3;
-typedef struct sqlite3_stmt sqlite3_stmt;
-
-namespace mbgl {
-
-namespace platform {
-struct Response;
-}
-
-enum class ResourceType : uint8_t {
- Unknown = 0,
- Tile = 1,
- Glyphs = 2,
- Image = 3,
- JSON = 4
-};
-
-class FileSource : private util::noncopyable, public std::enable_shared_from_this<FileSource> {
-public:
- FileSource();
- ~FileSource();
-
- void setBase(const std::string &value);
- const std::string &getBase() const;
-
- void load(ResourceType type, const std::string &url, std::function<void(platform::Response *)> callback, const std::shared_ptr<uv::loop> loop = nullptr);
-
-private:
- void closeDatabase();
- void createSchema();
- bool loadFile(ResourceType type, const std::string &url, std::function<void(platform::Response *)> callback);
- void saveFile(ResourceType type, const std::string &url, platform::Response *res);
-
-private:
- // Stores a URL that is used as a base for loading resources with relative path.
- std::string base;
-
- // Stores the absolute path to the cache directory.
- const std::string cache;
-
- sqlite3 *db = nullptr;
-};
-
-}
-
-#endif
diff --git a/include/mbgl/util/interpolate.hpp b/include/mbgl/util/interpolate.hpp
index e8c3389350..c9232db4eb 100644
--- a/include/mbgl/util/interpolate.hpp
+++ b/include/mbgl/util/interpolate.hpp
@@ -1,6 +1,8 @@
#ifndef MBGL_UTIL_INTERPOLATE
#define MBGL_UTIL_INTERPOLATE
+#include <array>
+
namespace mbgl {
namespace util {
diff --git a/include/mbgl/util/parsedate.h b/include/mbgl/util/parsedate.h
new file mode 100644
index 0000000000..6905e361d4
--- /dev/null
+++ b/include/mbgl/util/parsedate.h
@@ -0,0 +1,38 @@
+#ifndef HEADER_PARSEDATE_H
+#define HEADER_PARSEDATE_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2011, 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.
+ *
+ ***************************************************************************/
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <time.h>
+
+time_t parse_date(const char *p);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* HEADER_PARSEDATE_H */
diff --git a/include/mbgl/util/queue.h b/include/mbgl/util/queue.h
new file mode 100644
index 0000000000..fe02b454ea
--- /dev/null
+++ b/include/mbgl/util/queue.h
@@ -0,0 +1,92 @@
+/* Copyright (c) 2013, Ben Noordhuis <info@bnoordhuis.nl>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef QUEUE_H_
+#define QUEUE_H_
+
+typedef void *QUEUE[2];
+
+/* Private macros. */
+#define QUEUE_NEXT(q) (*(QUEUE **) &((*(q))[0]))
+#define QUEUE_PREV(q) (*(QUEUE **) &((*(q))[1]))
+#define QUEUE_PREV_NEXT(q) (QUEUE_NEXT(QUEUE_PREV(q)))
+#define QUEUE_NEXT_PREV(q) (QUEUE_PREV(QUEUE_NEXT(q)))
+
+/* Public macros. */
+#define QUEUE_DATA(ptr, type, field) \
+ ((type *) ((char *) (ptr) - ((char *) &((type *) 0)->field)))
+
+#define QUEUE_FOREACH(q, h) \
+ for ((q) = QUEUE_NEXT(h); (q) != (h); (q) = QUEUE_NEXT(q))
+
+#define QUEUE_EMPTY(q) \
+ ((const QUEUE *) (q) == (const QUEUE *) QUEUE_NEXT(q))
+
+#define QUEUE_HEAD(q) \
+ (QUEUE_NEXT(q))
+
+#define QUEUE_INIT(q) \
+ do { \
+ QUEUE_NEXT(q) = (q); \
+ QUEUE_PREV(q) = (q); \
+ } \
+ while (0)
+
+#define QUEUE_ADD(h, n) \
+ do { \
+ QUEUE_PREV_NEXT(h) = QUEUE_NEXT(n); \
+ QUEUE_NEXT_PREV(n) = QUEUE_PREV(h); \
+ QUEUE_PREV(h) = QUEUE_PREV(n); \
+ QUEUE_PREV_NEXT(h) = (h); \
+ } \
+ while (0)
+
+#define QUEUE_SPLIT(h, q, n) \
+ do { \
+ QUEUE_PREV(n) = QUEUE_PREV(h); \
+ QUEUE_PREV_NEXT(n) = (n); \
+ QUEUE_NEXT(n) = (q); \
+ QUEUE_PREV(h) = QUEUE_PREV(q); \
+ QUEUE_PREV_NEXT(h) = (h); \
+ QUEUE_PREV(q) = (n); \
+ } \
+ while (0)
+
+#define QUEUE_INSERT_HEAD(h, q) \
+ do { \
+ QUEUE_NEXT(q) = QUEUE_NEXT(h); \
+ QUEUE_PREV(q) = (h); \
+ QUEUE_NEXT_PREV(q) = (q); \
+ QUEUE_NEXT(h) = (q); \
+ } \
+ while (0)
+
+#define QUEUE_INSERT_TAIL(h, q) \
+ do { \
+ QUEUE_NEXT(q) = (h); \
+ QUEUE_PREV(q) = QUEUE_PREV(h); \
+ QUEUE_PREV_NEXT(q) = (q); \
+ QUEUE_PREV(h) = (q); \
+ } \
+ while (0)
+
+#define QUEUE_REMOVE(q) \
+ do { \
+ QUEUE_PREV_NEXT(q) = QUEUE_NEXT(q); \
+ QUEUE_NEXT_PREV(q) = QUEUE_PREV(q); \
+ } \
+ while (0)
+
+#endif /* QUEUE_H_ */
diff --git a/include/mbgl/util/sqlite3.hpp b/include/mbgl/util/sqlite3.hpp
new file mode 100644
index 0000000000..3e324f7ce1
--- /dev/null
+++ b/include/mbgl/util/sqlite3.hpp
@@ -0,0 +1,74 @@
+#pragma once
+
+#include <string>
+#include <stdexcept>
+
+typedef struct sqlite3 sqlite3;
+typedef struct sqlite3_stmt sqlite3_stmt;
+
+namespace mapbox {
+namespace sqlite {
+
+enum OpenFlag : int {
+ ReadOnly = 0x00000001,
+ ReadWrite = 0x00000002,
+ Create = 0x00000004,
+ NoMutex = 0x00008000,
+ FullMutex = 0x00010000,
+ SharedCache = 0x00020000,
+ PrivateCache = 0x00040000,
+};
+
+struct Exception : std::runtime_error {
+ inline Exception(int err, const char *msg) : std::runtime_error(msg), code(err) {}
+ const int code = 0;
+};
+
+class Statement;
+
+class Database {
+private:
+ Database(const Database &) = delete;
+ Database &operator=(const Database &) = delete;
+
+public:
+ Database(const std::string &filename, int flags = 0);
+ Database(Database &&);
+ ~Database();
+ Database &operator=(Database &&);
+
+ operator bool() const;
+
+ void exec(const std::string &sql);
+ Statement prepare(const char *query);
+
+private:
+ sqlite3 *db = nullptr;
+};
+
+class Statement {
+private:
+ Statement(const Statement &) = delete;
+ Statement &operator=(const Statement &) = delete;
+
+public:
+ Statement(sqlite3 *db, const char *sql);
+ Statement(Statement &&);
+ ~Statement();
+ Statement &operator=(Statement &&);
+
+ operator bool() const;
+
+ template <typename T> void bind(int offset, T value);
+ void bind(int offset, const std::string &value, bool retain = true);
+ template <typename T> T get(int offset);
+
+ bool run();
+ void reset();
+
+private:
+ sqlite3_stmt *stmt = nullptr;
+};
+
+}
+}
diff --git a/include/mbgl/util/uv-channel.h b/include/mbgl/util/uv-channel.h
new file mode 100644
index 0000000000..ea5c279f65
--- /dev/null
+++ b/include/mbgl/util/uv-channel.h
@@ -0,0 +1,29 @@
+#ifndef MBGL_UTIL_UV_CHANNEL
+#define MBGL_UTIL_UV_CHANNEL
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <uv.h>
+
+// Taken from http://navaneeth.github.io/blog/2013/08/02/channels-in-libuv/
+
+typedef struct uv_chan_s uv_chan_t;
+
+struct uv_chan_s {
+ uv_mutex_t mutex;
+ uv_cond_t cond;
+ void *q[2];
+};
+
+int uv_chan_init(uv_chan_t *chan);
+void uv_chan_send(uv_chan_t *chan, void *data);
+void *uv_chan_receive(uv_chan_t *chan);
+void uv_chan_destroy(uv_chan_t *chan);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/mbgl/util/uv-messenger.h b/include/mbgl/util/uv-messenger.h
new file mode 100644
index 0000000000..b082466b60
--- /dev/null
+++ b/include/mbgl/util/uv-messenger.h
@@ -0,0 +1,31 @@
+#ifndef MBGL_UTIL_UV_MESSENGER
+#define MBGL_UTIL_UV_MESSENGER
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <uv.h>
+
+typedef struct uv_messenger_s uv_messenger_t;
+typedef void (*uv_messenger_cb)(void *arg);
+typedef void (*uv_messenger_stop_cb)(uv_messenger_t *msgr);
+
+struct uv_messenger_s {
+ uv_mutex_t mutex;
+ uv_async_t async;
+ uv_messenger_cb callback;
+ void *data;
+ void *queue[2];
+};
+
+int uv_messenger_init(uv_loop_t *loop, uv_messenger_t *msgr, uv_messenger_cb callback);
+void uv_messenger_send(uv_messenger_t *msgr, void *arg);
+void uv_messenger_stop(uv_messenger_t *msgr);
+void uv_messenger_unref(uv_messenger_t *msgr);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/mbgl/util/uv-worker.h b/include/mbgl/util/uv-worker.h
new file mode 100644
index 0000000000..b9eb10fb70
--- /dev/null
+++ b/include/mbgl/util/uv-worker.h
@@ -0,0 +1,36 @@
+#ifndef MBGL_UTIL_UV_WORKER
+#define MBGL_UTIL_UV_WORKER
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct uv_messenger_s uv_messenger_t;
+
+#include <mbgl/util/uv-channel.h>
+
+#include <stdlib.h>
+
+typedef struct uv_worker_s uv_worker_t;
+
+struct uv_worker_s {
+ uv_thread_t thread;
+ uv_messenger_t *msgr;
+ uv_chan_t chan;
+ const char *name;
+};
+
+typedef void (*uv_worker_cb)(void *data);
+typedef void (*uv_worker_after_cb)(void *data);
+
+int uv_worker_init(uv_worker_t *worker, uv_loop_t *loop);
+int uv_worker_init_named(uv_worker_t *worker, uv_loop_t *loop, const char *name);
+void uv_worker_send(uv_worker_t *worker, void *data, uv_worker_cb work_cb,
+ uv_worker_after_cb after_work_cb);
+void uv_worker_close(uv_worker_t *worker);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/macosx/main.mm b/macosx/main.mm
index 9ecc307c05..7f22a32cc2 100644
--- a/macosx/main.mm
+++ b/macosx/main.mm
@@ -4,6 +4,8 @@
#import <Foundation/Foundation.h>
+#include <uv.h>
+
@interface URLHandler : NSObject
@property (nonatomic) mbgl::Map *map;
@@ -68,6 +70,7 @@
@end
int main() {
+ fprintf(stderr, "main thread: 0x%lx\n", uv_thread_self());
mbgl::Log::Set<mbgl::NSLogBackend>();
GLFWView view;
diff --git a/macosx/mapboxgl-app.gyp b/macosx/mapboxgl-app.gyp
index da5c68ea35..f673d68f0f 100644
--- a/macosx/mapboxgl-app.gyp
+++ b/macosx/mapboxgl-app.gyp
@@ -15,8 +15,7 @@
'../common/platform_nsstring.mm',
'../common/glfw_view.hpp',
'../common/glfw_view.cpp',
- '../common/foundation_request.h',
- '../common/foundation_request.mm',
+ '../src/storage/http_request_baton_darwin.mm',
'../common/nslog_log.hpp',
'../common/nslog_log.mm',
],
diff --git a/mapboxgl.gyp b/mapboxgl.gyp
index 34908aec59..f77e6d770f 100644
--- a/mapboxgl.gyp
+++ b/mapboxgl.gyp
@@ -143,6 +143,9 @@
'<@(sqlite3_cflags)',
'-I<(boost_root)/include',
],
+ 'OTHER_CFLAGS': [
+ '<@(uv_cflags)',
+ ],
},
}, {
'cflags': [
@@ -237,7 +240,10 @@
'<@(uv_libraries)',
'<@(curl_libraries)',
'<@(sqlite3_libraries)',
- ]
+ ],
+ 'OTHER_CFLAGS': [
+ '<@(uv_cflags)',
+ ],
}
}
}
diff --git a/src/map/map.cpp b/src/map/map.cpp
index 9984f1b181..1f9c1428f1 100644
--- a/src/map/map.cpp
+++ b/src/map/map.cpp
@@ -19,7 +19,7 @@
#include <mbgl/style/style_bucket.hpp>
#include <mbgl/util/texturepool.hpp>
#include <mbgl/geometry/sprite_atlas.hpp>
-#include <mbgl/util/filesource.hpp>
+#include <mbgl/storage/file_source.hpp>
#include <mbgl/platform/log.hpp>
#include <algorithm>
@@ -36,10 +36,7 @@ Map::Map(View& view)
thread(std::make_unique<uv::thread>()),
view(view),
transform(view),
- fileSource(std::make_shared<FileSource>()),
- style(std::make_shared<Style>()),
glyphAtlas(std::make_shared<GlyphAtlas>(1024, 1024)),
- glyphStore(std::make_shared<GlyphStore>(fileSource)),
spriteAtlas(std::make_shared<SpriteAtlas>(512, 512)),
texturepool(std::make_shared<Texturepool>()),
painter(*this) {
@@ -70,7 +67,7 @@ void Map::start() {
// Setup async notifications
async_terminate = new uv_async_t();
uv_async_init(**loop, async_terminate, terminate);
- async_terminate->data = **loop;
+ async_terminate->data = this;
async_render = new uv_async_t();
uv_async_init(**loop, async_render, render);
@@ -82,6 +79,9 @@ void Map::start() {
uv_thread_create(*thread, [](void *arg) {
Map *map = static_cast<Map *>(arg);
+#ifdef __APPLE__
+ pthread_setname_np("Map");
+#endif
map->run();
}, this);
}
@@ -173,8 +173,10 @@ void Map::render(uv_async_t *async) {
void Map::terminate(uv_async_t *async) {
// Closes all open handles on the loop. This means that the loop will automatically terminate.
- uv_loop_t *loop = static_cast<uv_loop_t *>(async->data);
- uv_walk(loop, [](uv_handle_t *handle, void */*arg*/) {
+ Map *map = static_cast<Map *>(async->data);
+ map->glyphStore.reset();
+ map->fileSource.reset();
+ uv_walk(**map->loop, [](uv_handle_t *handle, void */*arg*/) {
if (!uv_is_closing(handle)) {
uv_close(handle, NULL);
}
@@ -190,24 +192,13 @@ void Map::setup() {
}
void Map::setStyleURL(const std::string &url) {
- fileSource->load(ResourceType::JSON, url, [&](platform::Response *res) {
- if (res->code == 200) {
- // Calculate the base
- const size_t pos = url.rfind('/');
- std::string base = "";
- if (pos != std::string::npos) {
- base = url.substr(0, pos + 1);
- }
-
- this->setStyleJSON(res->body, base);
- } else {
- Log::Error(Event::Setup, "loading style failed: %d (%s)", res->code, res->error_message.c_str());
- }
- }, loop);
+ // TODO: Make threadsafe.
+ styleURL = url;
}
void Map::setStyleJSON(std::string newStyleJSON, const std::string &base) {
+ // TODO: Make threadsafe.
styleJSON.swap(newStyleJSON);
sprite.reset();
style->loadJSON((const uint8_t *)styleJSON.c_str());
@@ -221,6 +212,7 @@ std::string Map::getStyleJSON() const {
}
void Map::setAccessToken(std::string access_token) {
+ // TODO: Make threadsafe.
accessToken.swap(access_token);
}
@@ -522,6 +514,30 @@ void Map::updateRenderState() {
void Map::prepare() {
view.make_active();
+ if (!fileSource) {
+ fileSource = std::make_shared<FileSource>(**loop);
+ glyphStore = std::make_shared<GlyphStore>(fileSource);
+ }
+
+ if (!style) {
+ style = std::make_shared<Style>();
+
+ fileSource->request(ResourceType::JSON, styleURL)->onload([&](const Response &res) {
+ if (res.code == 200) {
+ // Calculate the base
+ const size_t pos = styleURL.rfind('/');
+ std::string base = "";
+ if (pos != std::string::npos) {
+ base = styleURL.substr(0, pos + 1);
+ }
+
+ setStyleJSON(res.data, base);
+ } else {
+ Log::Error(Event::Setup, "loading style failed: %ld (%s)", res.code, res.message.c_str());
+ }
+ });
+ }
+
// Update transform transitions.
animationTime = util::now();
if (transform.needsTransition()) {
diff --git a/src/map/source.cpp b/src/map/source.cpp
index 083e931b7a..50a276a165 100644
--- a/src/map/source.cpp
+++ b/src/map/source.cpp
@@ -6,7 +6,7 @@
#include <mbgl/util/raster.hpp>
#include <mbgl/util/string.hpp>
#include <mbgl/util/texturepool.hpp>
-#include <mbgl/util/filesource.hpp>
+#include <mbgl/storage/file_source.hpp>
#include <mbgl/util/vec.hpp>
#include <mbgl/util/math.hpp>
#include <mbgl/util/std.hpp>
@@ -37,14 +37,14 @@ void Source::load(Map& map) {
std::string url = util::mapbox::normalizeSourceURL(info.url, map.getAccessToken());
std::shared_ptr<Source> source = shared_from_this();
- map.getFileSource()->load(ResourceType::JSON, url, [source, &map](platform::Response *res) {
- if (res->code != 200) {
+ map.getFileSource()->request(ResourceType::JSON, url)->onload([source, &map](const Response &res) {
+ if (res.code != 200) {
Log::Warning(Event::General, "failed to load source TileJSON");
return;
}
rapidjson::Document d;
- d.Parse<0>(res->body.c_str());
+ d.Parse<0>(res.data.c_str());
if (d.HasParseError()) {
Log::Warning(Event::General, "invalid source TileJSON");
@@ -55,6 +55,7 @@ void Source::load(Map& map) {
source->loaded = true;
map.update();
+
});
}
diff --git a/src/map/sprite.cpp b/src/map/sprite.cpp
index af9413a0e3..c756ead159 100644
--- a/src/map/sprite.cpp
+++ b/src/map/sprite.cpp
@@ -5,7 +5,7 @@
#include <string>
#include <mbgl/platform/platform.hpp>
-#include <mbgl/util/filesource.hpp>
+#include <mbgl/storage/file_source.hpp>
#include <mbgl/util/uv_detail.hpp>
#include <mbgl/util/std.hpp>
@@ -62,28 +62,28 @@ void Sprite::load(const std::shared_ptr<FileSource> &fileSource) {
std::shared_ptr<Sprite> sprite = shared_from_this();
- fileSource->load(ResourceType::JSON, jsonURL, [sprite](platform::Response *res) {
- if (res->code == 200) {
- sprite->body.swap(res->body);
+ fileSource->request(ResourceType::JSON, jsonURL)->onload([sprite](const Response &res) {
+ if (res.code == 200) {
+ sprite->body = res.data;
sprite->parseJSON();
sprite->complete();
} else {
- Log::Warning(Event::Sprite, "Failed to load sprite info: Error %d: %s", res->code, res->error_message.c_str());
+ Log::Warning(Event::Sprite, "Failed to load sprite info: Error %d: %s", res.code, res.message.c_str());
if (!sprite->future.valid()) {
- sprite->promise.set_exception(std::make_exception_ptr(std::runtime_error(res->error_message)));
+ sprite->promise.set_exception(std::make_exception_ptr(std::runtime_error(res.message)));
}
}
});
- fileSource->load(ResourceType::Image, spriteURL, [sprite](platform::Response *res) {
- if (res->code == 200) {
- sprite->image.swap(res->body);
+ fileSource->request(ResourceType::Image, spriteURL)->onload([sprite](const Response &res) {
+ if (res.code == 200) {
+ sprite->image = res.data;
sprite->parseImage();
sprite->complete();
} else {
- Log::Warning(Event::Sprite, "Failed to load sprite image: Error %d: %s", res->code, res->error_message.c_str());
+ Log::Warning(Event::Sprite, "Failed to load sprite image: Error %d: %s", res.code, res.message.c_str());
if (!sprite->future.valid()) {
- sprite->promise.set_exception(std::make_exception_ptr(std::runtime_error(res->error_message)));
+ sprite->promise.set_exception(std::make_exception_ptr(std::runtime_error(res.message)));
}
}
});
diff --git a/src/map/tile_data.cpp b/src/map/tile_data.cpp
index 57afb8dadb..6c2869dfe8 100644
--- a/src/map/tile_data.cpp
+++ b/src/map/tile_data.cpp
@@ -4,7 +4,7 @@
#include <mbgl/util/token.hpp>
#include <mbgl/util/string.hpp>
-#include <mbgl/util/filesource.hpp>
+#include <mbgl/storage/file_source.hpp>
#include <mbgl/util/uv_detail.hpp>
using namespace mbgl;
@@ -45,21 +45,28 @@ void TileData::request() {
// Note: Somehow this feels slower than the change to request_http()
std::weak_ptr<TileData> weak_tile = shared_from_this();
- map.getFileSource()->load(ResourceType::Tile, url, [weak_tile, url](platform::Response *res) {
+ req = map.getFileSource()->request(ResourceType::Tile, url);
+ req->onload([weak_tile, url](const Response &res) {
std::shared_ptr<TileData> tile = weak_tile.lock();
if (!tile || tile->state == State::obsolete) {
// noop. Tile is obsolete and we're now just waiting for the refcount
// to drop to zero for destruction.
- } else if (res->code == 200) {
+ return;
+ }
+
+ // Clear the request object.
+ tile->req.reset();
+
+ if (res.code == 200) {
tile->state = State::loaded;
- tile->data.swap(res->body);
+ tile->data = res.data;
// Schedule tile parsing in another thread
tile->reparse();
} else {
#if defined(DEBUG)
- fprintf(stderr, "[%s] tile loading failed: %d, %s\n", url.c_str(), res->code, res->error_message.c_str());
+ fprintf(stderr, "[%s] tile loading failed: %ld, %s\n", url.c_str(), res.code, res.message.c_str());
#endif
}
});
@@ -68,7 +75,7 @@ void TileData::request() {
void TileData::cancel() {
if (state != State::obsolete) {
state = State::obsolete;
- platform::cancel_request_http(req.lock());
+ req.reset();
}
}
diff --git a/src/map/vector_tile_data.cpp b/src/map/vector_tile_data.cpp
index 1f74cab1d7..5181cbb2ce 100644
--- a/src/map/vector_tile_data.cpp
+++ b/src/map/vector_tile_data.cpp
@@ -30,18 +30,18 @@ void VectorTileData::parse() {
return;
}
- try {
+// try {
// Parsing creates state that is encapsulated in TileParser. While parsing,
// the TileParser object writes results into this objects. All other state
// is going to be discarded afterwards.
parser->parse();
- } catch (const std::exception& ex) {
-#if defined(DEBUG)
- fprintf(stderr, "[%p] exception [%d/%d/%d]... failed: %s\n", this, id.z, id.x, id.y, ex.what());
-#endif
- cancel();
- return;
- }
+// } catch (const std::exception& ex) {
+//#if defined(DEBUG)
+// fprintf(stderr, "[%p] exception [%d/%d/%d]... failed: %s\n", this, id.z, id.x, id.y, ex.what());
+//#endif
+// cancel();
+// return;
+// }
if (state != State::obsolete) {
state = State::parsed;
diff --git a/src/platform/request.cpp b/src/platform/request.cpp
deleted file mode 100644
index efd17895db..0000000000
--- a/src/platform/request.cpp
+++ /dev/null
@@ -1,54 +0,0 @@
-#include <mbgl/platform/request.hpp>
-#include <mbgl/platform/platform.hpp>
-#include <mbgl/util/std.hpp>
-#include <mbgl/util/uv_detail.hpp>
-
-using namespace mbgl::platform;
-
-Request::Request(const std::string &url,
- std::function<void(Response *)> callback,
- std::shared_ptr<uv::loop> loop)
- : url(url),
- res(std::make_unique<Response>(callback)),
- cancelled(false),
- loop(loop) {
- if (loop) {
- // Add a check handle without a callback to keep the default loop running.
- // We don't have a real handler attached to the default loop right from the
- // beginning, because we're using asynchronous messaging to perform the actual
- // request in the request thread. Only after the request is complete, we
- // create an actual work request that is attached to the default loop.
- async = new uv_async_t();
- async->data = new std::unique_ptr<Response>();
- uv_async_init(**loop, async, complete);
- }
-}
-
-Request::~Request() {
-}
-
-void Request::complete() {
- if (loop) {
- // We're scheduling the response callback to be invoked in the event loop.
- // Since the Request object will be deleted before the callback is invoked,
- // we move over the Response object to be owned by the async handle.
- ((std::unique_ptr<Response> *)async->data)->swap(res);
- uv_async_send(async);
- } else {
- // We're calling the response callback immediately. We're currently on an
- // arbitrary thread, but that's okay.
- res->callback(res.get());
- }
-}
-
-void Request::complete(uv_async_t *async) {
- Response *res = static_cast<std::unique_ptr<Response> *>(async->data)->get();
-
- res->callback(res);
-
- // We need to remove our async handle again to allow the main event loop to exit.
- uv_close((uv_handle_t *)async, [](uv_handle_t *handle) {
- delete static_cast<std::unique_ptr<Response> *>(handle->data);
- delete (uv_async_t *)handle;
- });
-}
diff --git a/src/platform/response.cpp b/src/platform/response.cpp
deleted file mode 100644
index ce376380fa..0000000000
--- a/src/platform/response.cpp
+++ /dev/null
@@ -1,31 +0,0 @@
-#include <mbgl/platform/response.hpp>
-#include <curl/curl.h>
-
-#include <cstdio>
-
-namespace mbgl {
-namespace platform {
-
-
-void Response::setCacheControl(const char *value) {
- if (!value) {
- expires = -1;
- return;
- }
-
- int seconds = 0;
- // TODO: cache-control may contain other information as well:
- // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
- if (std::sscanf(value, "max-age=%u", &seconds) == 1) {
- if (std::time(&expires) != -1) {
- expires += seconds;
- }
- }
-}
-
-void Response::setLastModified(const char *value) {
- modified = curl_getdate(value, nullptr);
-}
-
-}
-}
diff --git a/src/storage/base_request.cpp b/src/storage/base_request.cpp
new file mode 100644
index 0000000000..fb476e0982
--- /dev/null
+++ b/src/storage/base_request.cpp
@@ -0,0 +1,66 @@
+#include <mbgl/storage/base_request.hpp>
+#include <mbgl/storage/response.hpp>
+#include <mbgl/storage/request.hpp>
+
+#include <uv.h>
+
+#include <cassert>
+
+namespace mbgl {
+
+BaseRequest::BaseRequest() : thread_id(uv_thread_self()) {
+}
+
+BaseRequest::~BaseRequest() {
+ assert(thread_id == uv_thread_self());
+}
+
+void BaseRequest::notify() {
+ assert(thread_id == uv_thread_self());
+
+ // The parameter exists solely so that any calls to ->remove()
+ // are not going to cause deallocation of this object while this call is in progress.
+ std::shared_ptr<BaseRequest> retain = self;
+
+ // Swap the lists so that it's safe for callbacks to call ->cancel()
+ // on the request object, which would modify the list.
+ std::forward_list<std::unique_ptr<Callback>> list;
+ list.swap(callbacks);
+
+ // Only notify when we actually have a response. When the response code is 0, the request was
+ // canceled and we don't need to notify.
+ if (response) {
+ // Now that we have our private copy, notify all observers.
+ for (std::unique_ptr<Callback> &callback : list) {
+ (*callback)(*response);
+ }
+ }
+
+ self.reset();
+}
+
+Callback *BaseRequest::add(Callback &&callback, const std::shared_ptr<BaseRequest> &request) {
+ assert(thread_id == uv_thread_self());
+ assert(this == request.get());
+ if (response) {
+ // We already have a response. Notify right away.
+ callback(*response);
+ return nullptr;
+ } else {
+ self = request;
+ callbacks.push_front(std::unique_ptr<Callback>(new Callback(std::move(callback))));
+ return callbacks.front().get();
+ }
+}
+
+void BaseRequest::remove(Callback *callback) {
+ assert(thread_id == uv_thread_self());
+ callbacks.remove_if([=](const std::unique_ptr<Callback> &cb) {
+ return cb.get() == callback;
+ });
+ if (callbacks.empty()) {
+ self.reset();
+ }
+}
+
+}
diff --git a/src/storage/file_request.cpp b/src/storage/file_request.cpp
new file mode 100644
index 0000000000..ccbd896b9b
--- /dev/null
+++ b/src/storage/file_request.cpp
@@ -0,0 +1,179 @@
+#include <mbgl/storage/file_request.hpp>
+#include <mbgl/storage/response.hpp>
+
+#include <uv.h>
+
+#include <cassert>
+
+namespace mbgl {
+
+struct FileRequestBaton {
+ FileRequestBaton(FileRequest *request_, const std::string &path, uv_loop_t *loop)
+ : thread_id(uv_thread_self()), request(request_) {
+ req.data = this;
+ uv_fs_open(loop, &req, path.c_str(), O_RDONLY, S_IRUSR, file_opened);
+ }
+
+ ~FileRequestBaton() {
+ }
+
+ void cancel();
+ static void file_opened(uv_fs_t *req);
+ static void file_stated(uv_fs_t *req);
+ static void file_read(uv_fs_t *req);
+ static void file_closed(uv_fs_t *req);
+ static void notify_error(uv_fs_t *req);
+ static void cleanup(uv_fs_t *req);
+
+ const unsigned long thread_id;
+ FileRequest *request = nullptr;
+ uv_fs_t req;
+ uv_file fd = -1;
+ bool canceled = false;
+ std::string body;
+ uv_buf_t buffer;
+};
+
+void FileRequestBaton::cancel() {
+ canceled = true;
+
+ // uv_cancel fails frequently when the request has already been started.
+ // In that case, we have to let it complete and check the canceled bool
+ // instead.
+ uv_cancel((uv_req_t *)&req);
+}
+
+void FileRequestBaton::notify_error(uv_fs_t *req) {
+ FileRequestBaton *ptr = (FileRequestBaton *)req->data;
+ assert(ptr->thread_id == uv_thread_self());
+
+ if (ptr->request && req->result < 0 && !ptr->canceled && req->result != UV_ECANCELED) {
+ ptr->request->response = std::unique_ptr<Response>(new Response);
+ ptr->request->response->code = req->result == UV_ENOENT ? 404 : 500;
+ ptr->request->response->message = uv_strerror(int(req->result));
+ ptr->request->notify();
+ }
+}
+
+void FileRequestBaton::file_opened(uv_fs_t *req) {
+ FileRequestBaton *ptr = (FileRequestBaton *)req->data;
+ assert(ptr->thread_id == uv_thread_self());
+
+ if (req->result < 0) {
+ // Opening failed or was canceled. There isn't much left we can do.
+ notify_error(req);
+ cleanup(req);
+ } else {
+ const uv_file fd = uv_file(req->result);
+
+ // We're going to reuse this handle, so we need to cleanup first.
+ uv_fs_req_cleanup(req);
+
+ if (ptr->canceled || !ptr->request) {
+ // Either the FileRequest object has been destructed, or the
+ // request was canceled.
+ uv_fs_close(req->loop, req, fd, file_closed);
+ } else {
+ ptr->fd = fd;
+ uv_fs_fstat(req->loop, req, fd, file_stated);
+ }
+ }
+}
+
+void FileRequestBaton::file_stated(uv_fs_t *req) {
+ FileRequestBaton *ptr = (FileRequestBaton *)req->data;
+ assert(ptr->thread_id == uv_thread_self());
+
+ if (req->result < 0 || ptr->canceled || !ptr->request) {
+ // Stating failed or was canceled. We already have an open file handle
+ // though, which we'll have to close.
+ notify_error(req);
+
+ uv_fs_req_cleanup(req);
+ uv_fs_close(req->loop, req, ptr->fd, file_closed);
+ } else {
+ if (static_cast<const uv_stat_t *>(req->ptr)->st_size > std::numeric_limits<int>::max()) {
+ // File is too large for us to open this way because uv_buf's only support unsigned
+ // ints as maximum size.
+ if (ptr->request) {
+ ptr->request->response = std::unique_ptr<Response>(new Response);
+ ptr->request->response->code = UV_EFBIG;
+ ptr->request->response->message = uv_strerror(UV_EFBIG);
+ ptr->request->notify();
+ }
+
+ uv_fs_req_cleanup(req);
+ uv_fs_close(req->loop, req, ptr->fd, file_closed);
+ } else {
+ const unsigned int size =
+ (unsigned int)(static_cast<const uv_stat_t *>(req->ptr)->st_size);
+ ptr->body.resize(size);
+ ptr->buffer = uv_buf_init(const_cast<char *>(ptr->body.data()), size);
+ uv_fs_req_cleanup(req);
+ uv_fs_read(req->loop, req, ptr->fd, &ptr->buffer, 1, 0, file_read);
+ }
+ }
+}
+
+void FileRequestBaton::file_read(uv_fs_t *req) {
+ FileRequestBaton *ptr = (FileRequestBaton *)req->data;
+ assert(ptr->thread_id == uv_thread_self());
+
+ if (req->result < 0 || ptr->canceled || !ptr->request) {
+ // Stating failed or was canceled. We already have an open file handle
+ // though, which we'll have to close.
+ notify_error(req);
+ } else {
+ // File was successfully read.
+ if (ptr->request) {
+ ptr->request->response = std::unique_ptr<Response>(new Response);
+ ptr->request->response->code = 200;
+ ptr->request->response->data = std::move(ptr->body);
+ ptr->request->notify();
+ }
+ }
+
+ uv_fs_req_cleanup(req);
+ uv_fs_close(req->loop, req, ptr->fd, file_closed);
+}
+
+void FileRequestBaton::file_closed(uv_fs_t *req) {
+ FileRequestBaton *ptr = (FileRequestBaton *)req->data;
+ assert(ptr->thread_id == uv_thread_self());
+
+ if (req->result < 0) {
+ // Closing the file failed. But there isn't anything we can do.
+ }
+
+ cleanup(req);
+}
+
+void FileRequestBaton::cleanup(uv_fs_t *req) {
+ FileRequestBaton *ptr = (FileRequestBaton *)req->data;
+ assert(ptr->thread_id == uv_thread_self());
+
+ if (ptr->request) {
+ ptr->request->ptr = nullptr;
+ }
+
+ uv_fs_req_cleanup(req);
+ delete ptr;
+}
+
+FileRequest::FileRequest(const std::string &path_, uv_loop_t *loop)
+ : BaseRequest(), path(path_), thread_id(uv_thread_self()), ptr(new FileRequestBaton(this, path, loop)) {
+}
+
+FileRequest::~FileRequest() {
+ assert(thread_id == uv_thread_self());
+
+ if (ptr) {
+ ptr->cancel();
+
+ // When deleting a FileRequest object with a uv_fs_* call is in progress, we are making sure
+ // that the callback doesn't accidentally reference this object again.
+ ptr->request = nullptr;
+ }
+}
+
+}
diff --git a/src/storage/file_source.cpp b/src/storage/file_source.cpp
new file mode 100644
index 0000000000..1ee902aa7a
--- /dev/null
+++ b/src/storage/file_source.cpp
@@ -0,0 +1,80 @@
+#include <mbgl/storage/file_source.hpp>
+#include <mbgl/storage/file_request.hpp>
+#include <mbgl/storage/http_request.hpp>
+#include <mbgl/storage/sqlite_store.hpp>
+#include <mbgl/util/uv-messenger.h>
+
+#include <uv.h>
+
+namespace mbgl {
+
+FileSource::FileSource(uv_loop_t *loop_)
+ : thread_id(uv_thread_self()),
+ store(std::shared_ptr<SQLiteStore>(new SQLiteStore(loop_, "cache.db"))),
+ loop(loop_),
+ queue(new uv_messenger_t) {
+ uv_messenger_init(loop, queue, [](void *ptr) {
+ std::unique_ptr<std::function<void()>> fn { reinterpret_cast<std::function<void()> *>(ptr) };
+ (*fn)();
+ });
+}
+
+FileSource::~FileSource() {
+ assert(thread_id == uv_thread_self());
+ uv_messenger_stop(queue);
+}
+
+void FileSource::setBase(const std::string &value) {
+ assert(thread_id == uv_thread_self());
+ base = value;
+}
+
+const std::string &FileSource::getBase() const {
+ assert(thread_id == uv_thread_self());
+ return base;
+}
+
+std::unique_ptr<Request> FileSource::request(ResourceType type, const std::string &url) {
+ assert(thread_id == uv_thread_self());
+
+ // Make URL absolute.
+ const std::string absoluteURL = [&]() -> std::string {
+ const size_t separator = url.find("://");
+ if (separator == std::string::npos) {
+ // Relative URL.
+ return base + url;
+ } else {
+ return url;
+ }
+ }();
+
+ std::shared_ptr<BaseRequest> request;
+
+ // First, try to find an existing Request object.
+ auto it = pending.find(absoluteURL);
+ if (it != pending.end()) {
+ request = it->second.lock();
+ }
+
+ if (!request) {
+ if (absoluteURL.substr(0, 7) == "file://") {
+ request = std::make_shared<FileRequest>(absoluteURL.substr(7), loop);
+ } else {
+ request = std::make_shared<HTTPRequest>(type, absoluteURL, loop, store);
+ }
+
+ pending.emplace(absoluteURL, request);
+ }
+
+ return std::unique_ptr<Request>(new Request(request));
+}
+
+void FileSource::prepare(std::function<void()> fn) {
+ if (thread_id == uv_thread_self()) {
+ fn();
+ } else {
+ uv_messenger_send(queue, new std::function<void()>(std::move(fn)));
+ }
+}
+
+} \ No newline at end of file
diff --git a/src/storage/http_request.cpp b/src/storage/http_request.cpp
new file mode 100644
index 0000000000..56eef9268c
--- /dev/null
+++ b/src/storage/http_request.cpp
@@ -0,0 +1,112 @@
+#include <mbgl/storage/http_request.hpp>
+#include <mbgl/storage/request.hpp>
+#include <mbgl/storage/sqlite_store.hpp>
+#include <mbgl/storage/http_request_baton.hpp>
+
+#include <uv.h>
+
+#include <cassert>
+#include <chrono>
+
+namespace mbgl {
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdisabled-macro-expansion"
+#pragma clang diagnostic ignored "-Wexit-time-destructors"
+#pragma clang diagnostic ignored "-Wglobal-constructors"
+
+struct CacheRequestBaton {
+ HTTPRequest *request = nullptr;
+ std::string path;
+ uv_loop_t *loop;
+};
+
+HTTPRequest::HTTPRequest(ResourceType type_, const std::string &path, uv_loop_t *loop, std::shared_ptr<SQLiteStore> store_)
+ : BaseRequest(), thread_id(uv_thread_self()), store(store_), type(type_) {
+ cache_baton = new CacheRequestBaton;
+ cache_baton->request = this;
+ cache_baton->path = path;
+ cache_baton->loop = loop;
+ store->get(path, [](std::unique_ptr<Response> &&response, void *ptr) {
+ // Wrap in a unique_ptr, so it'll always get auto-destructed.
+ std::unique_ptr<CacheRequestBaton> baton((CacheRequestBaton *)ptr);
+ if (baton->request) {
+ assert(uv_thread_self() == baton->request->thread_id);
+ baton->request->loadedCacheEntry(std::move(response));
+ if (baton->request) {
+ // If we called notify(), the request object may already have ceased to exist.
+ baton->request->cache_baton = nullptr;
+ }
+ }
+ }, cache_baton);
+}
+
+void HTTPRequest::loadedCacheEntry(std::unique_ptr<Response> &&response) {
+ if (response) {
+ // This entry was stored in the cache. Now determine if we need to revalidate.
+ const int64_t now = std::chrono::duration_cast<std::chrono::seconds>(
+ std::chrono::system_clock::now().time_since_epoch()).count();
+ if (response->expires > now) {
+ if (cache_baton->request) {
+ cache_baton->request->response = std::move(response);
+ cache_baton->request->notify();
+ // Note: after calling notify(), the request object may cease to exist.
+ }
+ // This HTTPRequest is completed.
+ return;
+ } else {
+ // TODO: notify with preliminary results.
+ }
+ }
+
+ // Either, the cache entry is expired, or there is no cached version. In both cases,
+ // we need to do a new request. The requesting code will issue a conditional request in case
+ // we already have a modified date on the response object.
+ http_baton = new HTTPRequestBaton;
+ http_baton->request = this;
+ http_baton->path = std::move(cache_baton->path);
+ http_baton->async = new uv_async_t;
+ http_baton->response = std::move(response);
+ http_baton->async->data = http_baton;
+ uv_async_init(cache_baton->loop, http_baton->async, [](uv_async_t *async) {
+ std::unique_ptr<HTTPRequestBaton> baton((HTTPRequestBaton *)async->data);
+ if (baton->request) {
+ assert(uv_thread_self() == baton->request->thread_id);
+ baton->request->response = std::move(baton->response);
+ if (baton->not_modified) {
+ baton->request->store->updateExpiration(baton->path, baton->request->response->expires);
+ } else {
+ baton->request->store->put(baton->path, baton->request->type, *baton->request->response);
+ }
+ baton->request->notify();
+ // Note: after calling notify(), the baton object may cease to exist.
+ }
+
+ uv_close((uv_handle_t *)async, [](uv_handle_t *handle) {
+ delete (uv_async_t *)handle;
+ });
+ });
+ http_baton->start();
+}
+
+HTTPRequest::~HTTPRequest() {
+ assert(uv_thread_self() == thread_id);
+
+ if (cache_baton) {
+ // Make sure that this object doesn't accidentally get accessed when it is destructed before
+ // the callback returned. They are being run in the same thread, so just setting it to
+ // null is sufficient.
+ // Note: We don't manually delete the CacheRequestBaton since it'll be deleted by the
+ // callback.
+ cache_baton->request = nullptr;
+ }
+
+ if (http_baton) {
+ http_baton->request = nullptr;
+ http_baton->cancel();
+ }
+}
+
+#pragma clang diagnostic pop
+
+}
diff --git a/src/storage/http_request_baton_darwin.mm b/src/storage/http_request_baton_darwin.mm
new file mode 100644
index 0000000000..b1e47e3508
--- /dev/null
+++ b/src/storage/http_request_baton_darwin.mm
@@ -0,0 +1,103 @@
+#include <mbgl/storage/http_request_baton.hpp>
+#include <mbgl/util/parsedate.h>
+
+#include <uv.h>
+
+#import <Foundation/Foundation.h>
+#include <ctime>
+#include <xlocale.h>
+
+namespace mbgl {
+
+dispatch_once_t request_initialize = 0;
+NSURLSession *session = nullptr;
+
+void HTTPRequestBaton::start() {
+ // Starts the request.
+
+ // 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;
+
+ session = [NSURLSession sessionWithConfiguration:sessionConfig];
+ // TODO: add a delegate to the session that prohibits caching, since we handle this ourselves.
+ });
+
+
+ NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@(path.c_str())]];
+ if (response && response->modified) {
+ struct tm *timeinfo;
+ char buffer[32];
+ const time_t modified = response->modified;
+ timeinfo = std::gmtime (&modified);
+ 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) {
+ // TODO: Use different codes for host not found, timeout, invalid URL etc.
+ // These can be categorized in temporary and permanent errors.
+ response = std::unique_ptr<Response>(new Response);
+ response->code = -1;
+ response->message = [[error localizedDescription] UTF8String];
+ } else {
+ // This request was canceled.
+ // The response code remains at 0 to indicate cancelation.
+ }
+ } else if ([res isKindOfClass:[NSHTTPURLResponse class]]) {
+ const long code = [(NSHTTPURLResponse *)res statusCode];
+ if (code == 304) {
+ // Assume a Response object already exists.
+ assert(response);
+ not_modified = true;
+ } else {
+ response = std::unique_ptr<Response>(new Response);
+ response->code = code;
+ response->data = {(const char *)[data bytes], [data length]};
+ }
+
+ NSDictionary *headers = [(NSHTTPURLResponse *)res allHeaderFields];
+ NSString *cache_control = [headers objectForKey:@"Cache-Control"];
+ if (cache_control) {
+ response->expires = Response::parseCacheControl([cache_control UTF8String]);
+ }
+
+ NSString *last_modified = [headers objectForKey:@"Last-Modified"];
+ if (last_modified) {
+ response->modified = parse_date([last_modified UTF8String]);
+ }
+ } else {
+ // This should never happen.
+ response = std::unique_ptr<Response>(new Response);
+ response->code = -1;
+ response->message = "response class is not NSHTTPURLResponse";
+ // TODO: If we get a failure, we should not overwrite a potentially existing response.
+ }
+
+ uv_async_send(async);
+ }];
+
+ ptr = const_cast<void *>(CFBridgingRetain(task));
+
+ [task resume];
+}
+
+void HTTPRequestBaton::cancel() {
+ // After this function returns, the HTTPRequestBaton object may cease to exist at any time.
+ // try to stop the request
+ if (ptr) {
+ [(NSURLSessionDataTask *)CFBridgingRelease(ptr) cancel];
+ ptr = nullptr;
+ }
+}
+
+}
diff --git a/src/storage/request.cpp b/src/storage/request.cpp
new file mode 100644
index 0000000000..98e3f01237
--- /dev/null
+++ b/src/storage/request.cpp
@@ -0,0 +1,39 @@
+#include <mbgl/storage/request.hpp>
+#include <mbgl/storage/base_request.hpp>
+
+#include <uv.h>
+
+#include <cassert>
+
+namespace mbgl {
+
+Request::Request(const std::shared_ptr<BaseRequest> &base_)
+ : thread_id(uv_thread_self()), base(base_) {
+}
+
+Request::~Request() {
+ assert(thread_id == uv_thread_self());
+}
+
+void Request::onload(Callback cb) {
+ assert(thread_id == uv_thread_self());
+ if (base) {
+ Callback *callback = base->add(std::move(cb), base);
+ if (callback) {
+ callbacks.push_front(callback);
+ }
+ }
+}
+
+void Request::cancel() {
+ assert(thread_id == uv_thread_self());
+ if (base) {
+ for (Callback *callback : callbacks) {
+ base->remove(callback);
+ }
+ base.reset();
+ }
+ callbacks.clear();
+}
+
+}
diff --git a/src/storage/response.cpp b/src/storage/response.cpp
new file mode 100644
index 0000000000..cdaf33e4e4
--- /dev/null
+++ b/src/storage/response.cpp
@@ -0,0 +1,22 @@
+#include <mbgl/storage/response.hpp>
+
+#include <chrono>
+
+namespace mbgl {
+
+int64_t Response::parseCacheControl(const char *value) {
+ if (value) {
+ uint64_t seconds = 0;
+ // TODO: cache-control may contain other information as well:
+ // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
+ if (std::sscanf(value, "max-age=%llu", &seconds) == 1) {
+ return std::chrono::duration_cast<std::chrono::seconds>(
+ std::chrono::system_clock::now().time_since_epoch()).count() +
+ seconds;
+ }
+ }
+
+ return -1;
+}
+
+}
diff --git a/src/storage/sqlite_store.cpp b/src/storage/sqlite_store.cpp
new file mode 100644
index 0000000000..51e5aa1d21
--- /dev/null
+++ b/src/storage/sqlite_store.cpp
@@ -0,0 +1,194 @@
+#include <mbgl/storage/sqlite_store.hpp>
+#include <mbgl/util/compression.hpp>
+#include <mbgl/util/sqlite3.hpp>
+
+#include <mbgl/util/uv-worker.h>
+
+#include <cassert>
+
+using namespace mapbox::sqlite;
+
+std::string removeAccessTokenFromURL(const std::string &url) {
+ const size_t token_start = url.find("access_token=");
+ // Ensure that token exists, isn't at the front and is preceded by either & or ?.
+ if (token_start == std::string::npos || token_start == 0 || !(url[token_start - 1] == '&' || url[token_start - 1] == '?')) {
+ return url;
+ }
+
+ const size_t token_end = url.find_first_of('&', token_start);
+ if (token_end == std::string::npos) {
+ // The token is the last query argument. We slice away the "&access_token=..." part
+ return url.substr(0, token_start - 1);
+ } else {
+ // We slice away the "access_token=...&" part.
+ return url.substr(0, token_start) + url.substr(token_end + 1);
+ }
+}
+
+namespace mbgl {
+
+SQLiteStore::SQLiteStore(uv_loop_t *loop, const std::string &path)
+ : thread_id(uv_thread_self()),
+ db(std::make_shared<Database>(path.c_str(), ReadWrite | Create)) {
+ createSchema();
+ worker = new uv_worker_t;
+ uv_worker_init_named(worker, loop, "SQLite");
+}
+
+SQLiteStore::~SQLiteStore() {
+ // Nothing to do. This function needs to be here because we're forward-declaring
+ // Database, so we need the actual definition here to be able to properly destruct it.
+ if (worker) {
+ uv_worker_close(worker);
+ delete worker;
+ }
+}
+
+void SQLiteStore::createSchema() {
+ if (!db || !*db) {
+ return;
+ }
+
+ db->exec("CREATE TABLE IF NOT EXISTS `http_cache` ("
+ " `url` TEXT PRIMARY KEY NOT NULL,"
+ " `code` INTEGER NOT NULL,"
+ " `type` INTEGER NOT NULL,"
+ " `modified` INTEGER,"
+ " `expires` INTEGER,"
+ " `data` BLOB,"
+ " `compressed` INTEGER NOT NULL DEFAULT 0"
+ ");"
+ "CREATE INDEX IF NOT EXISTS `http_cache_type_idx` ON `http_cache` (`type`);");
+}
+
+struct GetBaton {
+ std::shared_ptr<Database> db;
+ std::string path;
+ ResourceType type;
+ void *ptr = nullptr;
+ SQLiteStore::GetCallback callback = nullptr;
+ std::unique_ptr<Response> response;
+};
+
+void SQLiteStore::get(const std::string &path, GetCallback callback, void *ptr) {
+ assert(uv_thread_self() == thread_id);
+ if (!db || !*db) {
+ if (callback) {
+ callback(nullptr, ptr);
+ }
+ return;
+ }
+
+ GetBaton *get_baton = new GetBaton;
+ get_baton->db = db;
+ get_baton->path = path;
+ get_baton->ptr = ptr;
+ get_baton->callback = callback;
+
+ uv_worker_send(worker, get_baton, [](void *data) {
+ GetBaton *baton = (GetBaton *)data;
+ const std::string url = removeAccessTokenFromURL(baton->path);
+ // 0 1 2
+ Statement stmt = baton->db->prepare("SELECT `code`, `type`, `modified`, "
+ // 3 4 5
+ "`expires`, `data`, `compressed` FROM `http_cache` WHERE `url` = ?");
+
+ stmt.bind(1, url.c_str());
+ if (stmt.run()) {
+ // There is data.
+ baton->response = std::unique_ptr<Response>(new Response);
+
+ baton->response->code = stmt.get<int>(0);
+ baton->type = ResourceType(stmt.get<int>(1));
+ baton->response->modified = stmt.get<int64_t>(2);
+ baton->response->expires = stmt.get<int64_t>(3);
+ baton->response->data = stmt.get<std::string>(4);
+ if (stmt.get<int>(5)) { // == compressed
+ baton->response->data = util::decompress(baton->response->data);
+ }
+ } else {
+ // There is no data.
+ // This is a noop.
+ }
+ }, [](void *data) {
+ std::unique_ptr<GetBaton> baton { (GetBaton *)data };
+ if (baton->callback) {
+ baton->callback(std::move(baton->response), baton->ptr);
+ }
+ });
+}
+
+
+struct PutBaton {
+ std::shared_ptr<Database> db;
+ std::string path;
+ ResourceType type;
+ Response response;
+};
+
+void SQLiteStore::put(const std::string &path, ResourceType type, const Response &response) {
+ assert(uv_thread_self() == thread_id);
+ if (!db) return;
+
+ PutBaton *put_baton = new PutBaton;
+ put_baton->db = db;
+ put_baton->path = path;
+ put_baton->type = type;
+ put_baton->response = response;
+
+ uv_worker_send(worker, put_baton, [](void *data) {
+ PutBaton *baton = (PutBaton *)data;
+ const std::string url = removeAccessTokenFromURL(baton->path);
+ Statement stmt = baton->db->prepare("REPLACE INTO `http_cache` ("
+ // 1 2 3 4 5 6 7
+ "`url`, `code`, `type`, `modified`, `expires`, `data`, `compressed`"
+ ") VALUES(?, ?, ?, ?, ?, ?, ?)");
+ stmt.bind(1, url.c_str());
+ stmt.bind(2, int(baton->response.code));
+ stmt.bind(3, int(baton->type));
+ stmt.bind(4, baton->response.modified);
+ stmt.bind(5, baton->response.expires);
+
+ if (baton->type == ResourceType::Image) {
+ stmt.bind(6, baton->response.data, false); // do not retain the string internally.
+ stmt.bind(7, false);
+ } else {
+ stmt.bind(6, util::compress(baton->response.data), true); // retain the string internally.
+ stmt.bind(7, true);
+ }
+
+ stmt.run();
+ }, [](void *data) {
+ delete (PutBaton *)data;
+ });
+}
+
+struct ExpirationBaton {
+ std::shared_ptr<Database> db;
+ std::string path;
+ int64_t expires;
+};
+
+void SQLiteStore::updateExpiration(const std::string &path, int64_t expires) {
+ assert(uv_thread_self() == thread_id);
+ if (!db || !*db) return;
+
+ ExpirationBaton *expiration_baton = new ExpirationBaton;
+ expiration_baton->db = db;
+ expiration_baton->path = path;
+ expiration_baton->expires = expires;
+
+ uv_worker_send(worker, expiration_baton, [](void *data) {
+ ExpirationBaton *baton = (ExpirationBaton *)data;
+ const std::string url = removeAccessTokenFromURL(baton->path);
+ Statement stmt = // 1 2
+ baton->db->prepare("UPDATE `http_cache` SET `expires` = ? WHERE `url` = ?");
+ stmt.bind<int64_t>(1, baton->expires);
+ stmt.bind(2, url.c_str());
+ stmt.run();
+ }, [](void *data) {
+ delete (ExpirationBaton *)data;
+ });
+}
+
+}
diff --git a/src/text/glyph_store.cpp b/src/text/glyph_store.cpp
index 783710d929..0ad6139ac9 100644
--- a/src/text/glyph_store.cpp
+++ b/src/text/glyph_store.cpp
@@ -8,7 +8,7 @@
#include <mbgl/util/constants.hpp>
#include <mbgl/util/token.hpp>
#include <mbgl/util/math.hpp>
-#include <mbgl/util/filesource.hpp>
+#include <mbgl/storage/file_source.hpp>
#include <mbgl/platform/platform.hpp>
#include <mbgl/util/uv_detail.hpp>
#include <algorithm>
@@ -147,23 +147,22 @@ GlyphPBF::GlyphPBF(const std::string &glyphURL, const std::string &fontStack, Gl
return "";
});
-#if defined(DEBUG)
- fprintf(stderr, "%s\n", url.c_str());
-#endif
-
- fileSource->load(ResourceType::Glyphs, url, [&](platform::Response *res) {
- if (res->code != 200) {
- // Something went wrong with loading the glyph pbf. Pass on the error to the future listeners.
- const std::string msg = util::sprintf<255>("[ERROR] failed to load glyphs (%d): %s\n", res->code, res->error_message.c_str());
- promise.set_exception(std::make_exception_ptr(std::runtime_error(msg)));
- } else {
- // Transfer the data to the GlyphSet and signal its availability.
- // Once it is available, the caller will need to call parse() to actually
- // parse the data we received. We are not doing this here since this callback is being
- // called from another (unknown) thread.
- data.swap(res->body);
- promise.set_value(*this);
- }
+ // The prepare call jumps back to the main thread.
+ fileSource->prepare([&, url, fileSource] {
+ fileSource->request(ResourceType::Glyphs, url)->onload([&, url](const Response &res) {
+ if (res.code != 200) {
+ // Something went wrong with loading the glyph pbf. Pass on the error to the future listeners.
+ const std::string msg = util::sprintf<255>("[ERROR] failed to load glyphs (%d): %s\n", res.code, res.message.c_str());
+ promise.set_exception(std::make_exception_ptr(std::runtime_error(msg)));
+ } else {
+ // Transfer the data to the GlyphSet and signal its availability.
+ // Once it is available, the caller will need to call parse() to actually
+ // parse the data we received. We are not doing this here since this callback is being
+ // called from another (unknown) thread.
+ data = res.data;
+ promise.set_value(*this);
+ }
+ });
});
}
diff --git a/src/util/filesource.cpp b/src/util/filesource.cpp
deleted file mode 100644
index 339b5b1e1d..0000000000
--- a/src/util/filesource.cpp
+++ /dev/null
@@ -1,296 +0,0 @@
-#include <mbgl/util/filesource.hpp>
-#include <mbgl/platform/platform.hpp>
-#include <mbgl/platform/log.hpp>
-#include <mbgl/util/compression.hpp>
-
-#include <sqlite3.h>
-
-#include <fstream>
-#include <sstream>
-
-namespace mbgl {
-
-FileSource::FileSource() {
- const int err = sqlite3_open_v2("cache.db", &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr);
- if (err != SQLITE_OK) {
- Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
- db = nullptr;
- }
-
- createSchema();
-}
-
-FileSource::~FileSource() {
- closeDatabase();
-}
-
-void FileSource::createSchema() {
- if (db) {
- // Create schema
- const int err = sqlite3_exec(db,
- "CREATE TABLE IF NOT EXISTS `http_cache` ("
- "`url` TEXT PRIMARY KEY NOT NULL,"
- "`code` INTEGER NOT NULL,"
- "`type` INTEGER NOT NULL,"
- "`modified` INTEGER,"
- "`expires` INTEGER,"
- "`data` BLOB,"
- "`compressed` INTEGER NOT NULL DEFAULT 0"
- ");"
- "CREATE INDEX IF NOT EXISTS `http_cache_type_idx` ON `http_cache` (`type`);", nullptr, nullptr, nullptr);
- if (err != SQLITE_OK) {
- Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
- closeDatabase();
- }
- }
-}
-
-void FileSource::closeDatabase() {
- if (db) {
- const int err = sqlite3_close_v2(db);
- if (err != SQLITE_OK) {
- Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
- }
- db = nullptr;
- }
-}
-
-void FileSource::setBase(const std::string &value) {
- base = value;
-}
-
-const std::string &FileSource::getBase() const {
- return base;
-}
-
-std::string removeAccessTokenFromURL(const std::string &url) {
- const size_t token_start = url.find("access_token=");
- // Ensure that token exists, isn't at the front and is preceded by either & or ?.
- if (token_start == std::string::npos || token_start == 0 || !(url[token_start - 1] == '&' || url[token_start - 1] == '?')) {
- return url;
- }
-
- const size_t token_end = url.find_first_of('&', token_start);
- if (token_end == std::string::npos) {
- // The token is the last query argument. We slice away the "&access_token=..." part
- return url.substr(0, token_start - 1);
- } else {
- // We slice away the "access_token=...&" part.
- return url.substr(0, token_start) + url.substr(token_end + 1);
- }
-}
-
-bool FileSource::loadFile(ResourceType /*type*/, const std::string &url, std::function<void(platform::Response *)> callback) {
- if (!db) {
- return false;
- }
-
- sqlite3_stmt *stmt = nullptr;
- int err;
-
- err = sqlite3_prepare_v2(db, "SELECT `code`, `data`, `compressed` FROM `http_cache` WHERE `url` = ?", -1, &stmt, nullptr);
- if (err != SQLITE_OK) {
- Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
- return false;
- }
-
- err = sqlite3_bind_text(stmt, 1, url.data(), url.size(), nullptr);
- if (err != SQLITE_OK) {
- Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
- err = sqlite3_finalize(stmt);
- if (err != SQLITE_OK) {
- Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
- }
- return false;
- }
-
- bool status = false;
-
- err = sqlite3_step(stmt);
- if (err == SQLITE_ROW) {
- // There is data.
- platform::Response res(callback);
- res.code = sqlite3_column_int(stmt, 0);
- const char *data = reinterpret_cast<const char *>(sqlite3_column_blob(stmt, 1));
- const size_t size = sqlite3_column_bytes(stmt, 1);
- const bool compressed = sqlite3_column_int(stmt, 2);
-
- if (compressed) {
- res.body = util::decompress({ data, size });
- } else {
- res.body = { data, size };
- }
-
- callback(&res);
- status = true;
- } else if (err == SQLITE_DONE) {
- // There is no data.
- // This is a noop.
- } else {
- // Another error occured.
- Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
- }
-
- err = sqlite3_finalize(stmt);
- if (err != SQLITE_OK) {
- Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
- }
- return status;
-}
-
-void FileSource::saveFile(ResourceType type, const std::string &url, platform::Response *const res) {
- if (!db) {
- return;
- }
-
- sqlite3_stmt *stmt = nullptr;
- int err;
-
- err = sqlite3_prepare_v2(db, "REPLACE INTO `http_cache` (`url`, `code`, `type`, `modified`, `expires`, `data`, `compressed`) VALUES(?, ?, ?, ?, ?, ?, ?)", -1, &stmt, nullptr);
- if (err != SQLITE_OK) {
- Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
- return;
- }
-
- err = sqlite3_bind_text(stmt, 1, url.data(), url.size(), nullptr);
- if (err != SQLITE_OK) {
- Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
- err = sqlite3_finalize(stmt);
- if (err != SQLITE_OK) {
- Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
- }
- return;
- }
-
- err = sqlite3_bind_int(stmt, 2, res->code);
- if (err != SQLITE_OK) {
- Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
- err = sqlite3_finalize(stmt);
- if (err != SQLITE_OK) {
- Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
- }
- return;
- }
-
- err = sqlite3_bind_int(stmt, 3, int(type));
- if (err != SQLITE_OK) {
- Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
- err = sqlite3_finalize(stmt);
- if (err != SQLITE_OK) {
- Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
- }
- return;
- }
-
- err = sqlite3_bind_int(stmt, 4, res->modified);
- if (err != SQLITE_OK) {
- Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
- err = sqlite3_finalize(stmt);
- if (err != SQLITE_OK) {
- Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
- }
- return;
- }
-
- err = sqlite3_bind_int(stmt, 5, res->expires);
- if (err != SQLITE_OK) {
- Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
- err = sqlite3_finalize(stmt);
- if (err != SQLITE_OK) {
- Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
- }
- return;
- }
-
- std::string data;
- bool compressed = false;
- switch (type) {
- case ResourceType::Image:
- err = sqlite3_bind_blob(stmt, 6, res->body.data(), res->body.size(), SQLITE_STATIC);
- break;
- default:
- data = std::move(util::compress(res->body));
- compressed = true;
- err = sqlite3_bind_blob(stmt, 6, data.data(), data.size(), SQLITE_STATIC);
- break;
- }
- if (err != SQLITE_OK) {
- Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
- err = sqlite3_finalize(stmt);
- if (err != SQLITE_OK) {
- Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
- }
- return;
- }
-
- err = sqlite3_bind_int(stmt, 7, compressed);
- if (err != SQLITE_OK) {
- Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
- err = sqlite3_finalize(stmt);
- if (err != SQLITE_OK) {
- Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
- }
- return;
- }
-
- err = sqlite3_step(stmt);
- if (err != SQLITE_DONE) {
- // Another error occured.
- Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
- }
-
- err = sqlite3_finalize(stmt);
- if (err != SQLITE_OK) {
- Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
- }
-}
-
-
-void FileSource::load(ResourceType type, const std::string &url, std::function<void(platform::Response *)> callback, const std::shared_ptr<uv::loop> loop) {
- // convert relative URLs to absolute URLs
-
- const std::string absoluteURL = [&]() -> std::string {
- const size_t separator = url.find("://");
- if (separator == std::string::npos) {
- // Relative URL.
- return base + url;
- } else {
- return url;
- }
- }();
-
- const size_t separator = absoluteURL.find("://");
- const std::string protocol = separator != std::string::npos ? absoluteURL.substr(0, separator) : "";
-
- if (protocol == "file") {
- // load from disk
- const std::string path = absoluteURL.substr(separator + 3);
- std::ifstream file(path);
-
- platform::Response response(callback);
- if (!file.good()) {
- response.error_message = "file not found (" + path + ")";
- } else {
- std::stringstream data;
- data << file.rdbuf();
- response.code = 200;
- response.body = data.str();
- }
-
- callback(&response);
- } else {
- // Don't use the access token when storing/retrieving URLs from the database
- const std::string cleanURL = removeAccessTokenFromURL(url);
-
- // load from the internet
- if (!loadFile(type, cleanURL, callback)) {
- FileSource *source = this;
- platform::request_http(absoluteURL, [=](platform::Response *res) {
- source->saveFile(type, cleanURL, res);
- callback(res);
- }, loop);
- }
- }
-}
-
-} \ No newline at end of file
diff --git a/src/util/parsedate.c b/src/util/parsedate.c
new file mode 100644
index 0000000000..123c5c4e5f
--- /dev/null
+++ b/src/util/parsedate.c
@@ -0,0 +1,689 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2014, 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.
+ *
+ ***************************************************************************/
+/*
+ A brief summary of the date string formats this parser groks:
+
+ RFC 2616 3.3.1
+
+ Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
+ Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
+ Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
+
+ we support dates without week day name:
+
+ 06 Nov 1994 08:49:37 GMT
+ 06-Nov-94 08:49:37 GMT
+ Nov 6 08:49:37 1994
+
+ without the time zone:
+
+ 06 Nov 1994 08:49:37
+ 06-Nov-94 08:49:37
+
+ weird order:
+
+ 1994 Nov 6 08:49:37 (GNU date fails)
+ GMT 08:49:37 06-Nov-94 Sunday
+ 94 6 Nov 08:49:37 (GNU date fails)
+
+ time left out:
+
+ 1994 Nov 6
+ 06-Nov-94
+ Sun Nov 6 94
+
+ unusual separators:
+
+ 1994.Nov.6
+ Sun/Nov/6/94/GMT
+
+ commonly used time zone names:
+
+ Sun, 06 Nov 1994 08:49:37 CET
+ 06 Nov 1994 08:49:37 EST
+
+ time zones specified using RFC822 style:
+
+ Sun, 12 Sep 2004 15:05:58 -0700
+ Sat, 11 Sep 2004 21:32:11 +0200
+
+ compact numerical date strings:
+
+ 20040912 15:05:58 -0700
+ 20040911 +0200
+
+*/
+
+#include <mbgl/util/parsedate.h>
+
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <limits.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+
+#define ERRNO (errno)
+#define SET_ERRNO(x) (errno = (x))
+
+
+/* Portable, consistent toupper (remember EBCDIC). Do not use toupper() because
+ its behavior is altered by the current locale. */
+char raw_toupper(char in)
+{
+ switch (in) {
+ case 'a':
+ return 'A';
+ case 'b':
+ return 'B';
+ case 'c':
+ return 'C';
+ case 'd':
+ return 'D';
+ case 'e':
+ return 'E';
+ case 'f':
+ return 'F';
+ case 'g':
+ return 'G';
+ case 'h':
+ return 'H';
+ case 'i':
+ return 'I';
+ case 'j':
+ return 'J';
+ case 'k':
+ return 'K';
+ case 'l':
+ return 'L';
+ case 'm':
+ return 'M';
+ case 'n':
+ return 'N';
+ case 'o':
+ return 'O';
+ case 'p':
+ return 'P';
+ case 'q':
+ return 'Q';
+ case 'r':
+ return 'R';
+ case 's':
+ return 'S';
+ case 't':
+ return 'T';
+ case 'u':
+ return 'U';
+ case 'v':
+ return 'V';
+ case 'w':
+ return 'W';
+ case 'x':
+ return 'X';
+ case 'y':
+ return 'Y';
+ case 'z':
+ return 'Z';
+ }
+ return in;
+}
+
+/*
+ * raw_equal() is for doing "raw" case insensitive strings. This is meant
+ * to be locale independent and only compare strings we know are safe for
+ * this. See http://daniel.haxx.se/blog/2008/10/15/strcasecmp-in-turkish/ for
+ * some further explanation to why this function is necessary.
+ *
+ * The function is capable of comparing a-z case insensitively even for
+ * non-ascii.
+ */
+
+int raw_equal(const char *first, const char *second)
+{
+ while(*first && *second) {
+ if(raw_toupper(*first) != raw_toupper(*second))
+ /* get out of the loop as soon as they don't match */
+ break;
+ first++;
+ second++;
+ }
+ /* we do the comparison here (possibly again), just to make sure that if the
+ loop above is skipped because one of the strings reached zero, we must not
+ return this as a successful match */
+ return (raw_toupper(*first) == raw_toupper(*second));
+}
+
+#define ISSPACE(x) (isspace((int) ((unsigned char)x)))
+#define ISDIGIT(x) (isdigit((int) ((unsigned char)x)))
+#define ISALNUM(x) (isalnum((int) ((unsigned char)x)))
+#define ISALPHA(x) (isalpha((int) ((unsigned char)x)))
+
+
+/*
+ * Redefine TRUE and FALSE too, to catch current use. With this
+ * change, 'bool found = 1' will give a warning on MIPSPro, but
+ * 'bool found = TRUE' will not. Change tested on IRIX/MIPSPro,
+ * AIX 5.1/Xlc, Tru64 5.1/cc, w/make test too.
+ */
+
+#ifndef TRUE
+#define TRUE true
+#endif
+#ifndef FALSE
+#define FALSE false
+#endif
+
+
+
+/*
+** signed long to signed int
+*/
+
+int clamp_to_int(long slnum)
+{
+ return slnum > INT_MAX ? INT_MAX : slnum < INT_MIN ? INT_MIN : slnum;
+}
+
+
+const char * const wkday[] =
+{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
+static const char * const weekday[] =
+{ "Monday", "Tuesday", "Wednesday", "Thursday",
+ "Friday", "Saturday", "Sunday" };
+const char * const month[]=
+{ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+
+struct tzinfo {
+ char name[5];
+ int offset; /* +/- in minutes */
+};
+
+/*
+ * parsedate()
+ *
+ * Returns:
+ *
+ * PARSEDATE_OK - a fine conversion
+ * PARSEDATE_FAIL - failed to convert
+ * PARSEDATE_LATER - time overflow at the far end of time_t
+ * PARSEDATE_SOONER - time underflow at the low end of time_t
+ */
+
+static int parsedate(const char *date, time_t *output);
+
+#define PARSEDATE_OK 0
+#define PARSEDATE_FAIL -1
+#define PARSEDATE_LATER 1
+#define PARSEDATE_SOONER 2
+
+/* Here's a bunch of frequently used time zone names. These were supported
+ by the old getdate parser. */
+#define tDAYZONE -60 /* offset for daylight savings time */
+static const struct tzinfo tz[]= {
+ {"GMT", 0}, /* Greenwich Mean */
+ {"UTC", 0}, /* Universal (Coordinated) */
+ {"WET", 0}, /* Western European */
+ {"BST", 0 tDAYZONE}, /* British Summer */
+ {"WAT", 60}, /* West Africa */
+ {"AST", 240}, /* Atlantic Standard */
+ {"ADT", 240 tDAYZONE}, /* Atlantic Daylight */
+ {"EST", 300}, /* Eastern Standard */
+ {"EDT", 300 tDAYZONE}, /* Eastern Daylight */
+ {"CST", 360}, /* Central Standard */
+ {"CDT", 360 tDAYZONE}, /* Central Daylight */
+ {"MST", 420}, /* Mountain Standard */
+ {"MDT", 420 tDAYZONE}, /* Mountain Daylight */
+ {"PST", 480}, /* Pacific Standard */
+ {"PDT", 480 tDAYZONE}, /* Pacific Daylight */
+ {"YST", 540}, /* Yukon Standard */
+ {"YDT", 540 tDAYZONE}, /* Yukon Daylight */
+ {"HST", 600}, /* Hawaii Standard */
+ {"HDT", 600 tDAYZONE}, /* Hawaii Daylight */
+ {"CAT", 600}, /* Central Alaska */
+ {"AHST", 600}, /* Alaska-Hawaii Standard */
+ {"NT", 660}, /* Nome */
+ {"IDLW", 720}, /* International Date Line West */
+ {"CET", -60}, /* Central European */
+ {"MET", -60}, /* Middle European */
+ {"MEWT", -60}, /* Middle European Winter */
+ {"MEST", -60 tDAYZONE}, /* Middle European Summer */
+ {"CEST", -60 tDAYZONE}, /* Central European Summer */
+ {"MESZ", -60 tDAYZONE}, /* Middle European Summer */
+ {"FWT", -60}, /* French Winter */
+ {"FST", -60 tDAYZONE}, /* French Summer */
+ {"EET", -120}, /* Eastern Europe, USSR Zone 1 */
+ {"WAST", -420}, /* West Australian Standard */
+ {"WADT", -420 tDAYZONE}, /* West Australian Daylight */
+ {"CCT", -480}, /* China Coast, USSR Zone 7 */
+ {"JST", -540}, /* Japan Standard, USSR Zone 8 */
+ {"EAST", -600}, /* Eastern Australian Standard */
+ {"EADT", -600 tDAYZONE}, /* Eastern Australian Daylight */
+ {"GST", -600}, /* Guam Standard, USSR Zone 9 */
+ {"NZT", -720}, /* New Zealand */
+ {"NZST", -720}, /* New Zealand Standard */
+ {"NZDT", -720 tDAYZONE}, /* New Zealand Daylight */
+ {"IDLE", -720}, /* International Date Line East */
+ /* Next up: Military timezone names. RFC822 allowed these, but (as noted in
+ RFC 1123) had their signs wrong. Here we use the correct signs to match
+ actual military usage.
+ */
+ {"A", +1 * 60}, /* Alpha */
+ {"B", +2 * 60}, /* Bravo */
+ {"C", +3 * 60}, /* Charlie */
+ {"D", +4 * 60}, /* Delta */
+ {"E", +5 * 60}, /* Echo */
+ {"F", +6 * 60}, /* Foxtrot */
+ {"G", +7 * 60}, /* Golf */
+ {"H", +8 * 60}, /* Hotel */
+ {"I", +9 * 60}, /* India */
+ /* "J", Juliet is not used as a timezone, to indicate the observer's local
+ time */
+ {"K", +10 * 60}, /* Kilo */
+ {"L", +11 * 60}, /* Lima */
+ {"M", +12 * 60}, /* Mike */
+ {"N", -1 * 60}, /* November */
+ {"O", -2 * 60}, /* Oscar */
+ {"P", -3 * 60}, /* Papa */
+ {"Q", -4 * 60}, /* Quebec */
+ {"R", -5 * 60}, /* Romeo */
+ {"S", -6 * 60}, /* Sierra */
+ {"T", -7 * 60}, /* Tango */
+ {"U", -8 * 60}, /* Uniform */
+ {"V", -9 * 60}, /* Victor */
+ {"W", -10 * 60}, /* Whiskey */
+ {"X", -11 * 60}, /* X-ray */
+ {"Y", -12 * 60}, /* Yankee */
+ {"Z", 0}, /* Zulu, zero meridian, a.k.a. UTC */
+};
+
+/* returns:
+ -1 no day
+ 0 monday - 6 sunday
+*/
+
+static int checkday(const char *check, size_t len)
+{
+ int i;
+ const char * const *what;
+ bool found= FALSE;
+ if(len > 3)
+ what = &weekday[0];
+ else
+ what = &wkday[0];
+ for(i=0; i<7; i++) {
+ if(raw_equal(check, what[0])) {
+ found=TRUE;
+ break;
+ }
+ what++;
+ }
+ return found?i:-1;
+}
+
+static int checkmonth(const char *check)
+{
+ int i;
+ const char * const *what;
+ bool found= FALSE;
+
+ what = &month[0];
+ for(i=0; i<12; i++) {
+ if(raw_equal(check, what[0])) {
+ found=TRUE;
+ break;
+ }
+ what++;
+ }
+ return found?i:-1; /* return the offset or -1, no real offset is -1 */
+}
+
+/* return the time zone offset between GMT and the input one, in number
+ of seconds or -1 if the timezone wasn't found/legal */
+
+static int checktz(const char *check)
+{
+ unsigned int i;
+ const struct tzinfo *what;
+ bool found= FALSE;
+
+ what = tz;
+ for(i=0; i< sizeof(tz)/sizeof(tz[0]); i++) {
+ if(raw_equal(check, what->name)) {
+ found=TRUE;
+ break;
+ }
+ what++;
+ }
+ return found?what->offset*60:-1;
+}
+
+static void skip(const char **date)
+{
+ /* skip everything that aren't letters or digits */
+ while(**date && !ISALNUM(**date))
+ (*date)++;
+}
+
+enum assume {
+ DATE_MDAY,
+ DATE_YEAR,
+ DATE_TIME
+};
+
+/* this is a clone of 'struct tm' but with all fields we don't need or use
+ cut out */
+struct my_tm {
+ int tm_sec;
+ int tm_min;
+ int tm_hour;
+ int tm_mday;
+ int tm_mon;
+ int tm_year;
+};
+
+/* struct tm to time since epoch in GMT time zone.
+ * This is similar to the standard mktime function but for GMT only, and
+ * doesn't suffer from the various bugs and portability problems that
+ * some systems' implementations have.
+ */
+static time_t my_timegm(struct my_tm *tm)
+{
+ static const int month_days_cumulative [12] =
+ { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
+ int month, year, leap_days;
+
+ if(tm->tm_year < 70)
+ /* we don't support years before 1970 as they will cause this function
+ to return a negative value */
+ return -1;
+
+ year = tm->tm_year + 1900;
+ month = tm->tm_mon;
+ if(month < 0) {
+ year += (11 - month) / 12;
+ month = 11 - (11 - month) % 12;
+ }
+ else if(month >= 12) {
+ year -= month / 12;
+ month = month % 12;
+ }
+
+ leap_days = year - (tm->tm_mon <= 1);
+ leap_days = ((leap_days / 4) - (leap_days / 100) + (leap_days / 400)
+ - (1969 / 4) + (1969 / 100) - (1969 / 400));
+
+ return ((((time_t) (year - 1970) * 365
+ + leap_days + month_days_cumulative [month] + tm->tm_mday - 1) * 24
+ + tm->tm_hour) * 60 + tm->tm_min) * 60 + tm->tm_sec;
+}
+
+/*
+ * parsedate()
+ *
+ * Returns:
+ *
+ * PARSEDATE_OK - a fine conversion
+ * PARSEDATE_FAIL - failed to convert
+ * PARSEDATE_LATER - time overflow at the far end of time_t
+ * PARSEDATE_SOONER - time underflow at the low end of time_t
+ */
+
+static int parsedate(const char *date, time_t *output)
+{
+ time_t t = 0;
+ int wdaynum=-1; /* day of the week number, 0-6 (mon-sun) */
+ int monnum=-1; /* month of the year number, 0-11 */
+ int mdaynum=-1; /* day of month, 1 - 31 */
+ int hournum=-1;
+ int minnum=-1;
+ int secnum=-1;
+ int yearnum=-1;
+ int tzoff=-1;
+ struct my_tm tm;
+ enum assume dignext = DATE_MDAY;
+ const char *indate = date; /* save the original pointer */
+ int part = 0; /* max 6 parts */
+
+ while(*date && (part < 6)) {
+ bool found=FALSE;
+
+ skip(&date);
+
+ if(ISALPHA(*date)) {
+ /* a name coming up */
+ char buf[32]="";
+ size_t len;
+ if(sscanf(date, "%31[ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz]", buf))
+ len = strlen(buf);
+ else
+ len = 0;
+
+ if(wdaynum == -1) {
+ wdaynum = checkday(buf, len);
+ if(wdaynum != -1)
+ found = TRUE;
+ }
+ if(!found && (monnum == -1)) {
+ monnum = checkmonth(buf);
+ if(monnum != -1)
+ found = TRUE;
+ }
+
+ if(!found && (tzoff == -1)) {
+ /* this just must be a time zone string */
+ tzoff = checktz(buf);
+ if(tzoff != -1)
+ found = TRUE;
+ }
+
+ if(!found)
+ return PARSEDATE_FAIL; /* bad string */
+
+ date += len;
+ }
+ else if(ISDIGIT(*date)) {
+ /* a digit */
+ int val;
+ char *end;
+ if((secnum == -1) &&
+ (3 == sscanf(date, "%02d:%02d:%02d", &hournum, &minnum, &secnum))) {
+ /* time stamp! */
+ date += 8;
+ }
+ else if((secnum == -1) &&
+ (2 == sscanf(date, "%02d:%02d", &hournum, &minnum))) {
+ /* time stamp without seconds */
+ date += 5;
+ secnum = 0;
+ }
+ else {
+ long lval;
+ int error;
+ int old_errno;
+
+ old_errno = ERRNO;
+ SET_ERRNO(0);
+ lval = strtol(date, &end, 10);
+ error = ERRNO;
+ if(error != old_errno)
+ SET_ERRNO(old_errno);
+
+ if(error)
+ return PARSEDATE_FAIL;
+
+#if LONG_MAX != INT_MAX
+ if((lval > (long)INT_MAX) || (lval < (long)INT_MIN))
+ return PARSEDATE_FAIL;
+#endif
+
+ val = clamp_to_int(lval);
+
+ if((tzoff == -1) &&
+ ((end - date) == 4) &&
+ (val <= 1400) &&
+ (indate< date) &&
+ ((date[-1] == '+' || date[-1] == '-'))) {
+ /* four digits and a value less than or equal to 1400 (to take into
+ account all sorts of funny time zone diffs) and it is preceded
+ with a plus or minus. This is a time zone indication. 1400 is
+ picked since +1300 is frequently used and +1400 is mentioned as
+ an edge number in the document "ISO C 200X Proposal: Timezone
+ Functions" at http://david.tribble.com/text/c0xtimezone.html If
+ anyone has a more authoritative source for the exact maximum time
+ zone offsets, please speak up! */
+ found = TRUE;
+ tzoff = (val/100 * 60 + val%100)*60;
+
+ /* the + and - prefix indicates the local time compared to GMT,
+ this we need ther reversed math to get what we want */
+ tzoff = date[-1]=='+'?-tzoff:tzoff;
+ }
+
+ if(((end - date) == 8) &&
+ (yearnum == -1) &&
+ (monnum == -1) &&
+ (mdaynum == -1)) {
+ /* 8 digits, no year, month or day yet. This is YYYYMMDD */
+ found = TRUE;
+ yearnum = val/10000;
+ monnum = (val%10000)/100-1; /* month is 0 - 11 */
+ mdaynum = val%100;
+ }
+
+ if(!found && (dignext == DATE_MDAY) && (mdaynum == -1)) {
+ if((val > 0) && (val<32)) {
+ mdaynum = val;
+ found = TRUE;
+ }
+ dignext = DATE_YEAR;
+ }
+
+ if(!found && (dignext == DATE_YEAR) && (yearnum == -1)) {
+ yearnum = val;
+ found = TRUE;
+ if(yearnum < 1900) {
+ if(yearnum > 70)
+ yearnum += 1900;
+ else
+ yearnum += 2000;
+ }
+ if(mdaynum == -1)
+ dignext = DATE_MDAY;
+ }
+
+ if(!found)
+ return PARSEDATE_FAIL;
+
+ date = end;
+ }
+ }
+
+ part++;
+ }
+
+ if(-1 == secnum)
+ secnum = minnum = hournum = 0; /* no time, make it zero */
+
+ if((-1 == mdaynum) ||
+ (-1 == monnum) ||
+ (-1 == yearnum))
+ /* lacks vital info, fail */
+ return PARSEDATE_FAIL;
+
+#if SIZEOF_TIME_T < 5
+ /* 32 bit time_t can only hold dates to the beginning of 2038 */
+ if(yearnum > 2037) {
+ *output = 0x7fffffff;
+ return PARSEDATE_LATER;
+ }
+#endif
+
+ if(yearnum < 1970) {
+ *output = 0;
+ return PARSEDATE_SOONER;
+ }
+
+ if((mdaynum > 31) || (monnum > 11) ||
+ (hournum > 23) || (minnum > 59) || (secnum > 60))
+ return PARSEDATE_FAIL; /* clearly an illegal date */
+
+ tm.tm_sec = secnum;
+ tm.tm_min = minnum;
+ tm.tm_hour = hournum;
+ tm.tm_mday = mdaynum;
+ tm.tm_mon = monnum;
+ tm.tm_year = yearnum - 1900;
+
+ /* my_timegm() returns a time_t. time_t is often 32 bits, even on many
+ architectures that feature 64 bit 'long'.
+
+ Some systems have 64 bit time_t and deal with years beyond 2038. However,
+ even on some of the systems with 64 bit time_t mktime() returns -1 for
+ dates beyond 03:14:07 UTC, January 19, 2038. (Such as AIX 5100-06)
+ */
+ t = my_timegm(&tm);
+
+ /* time zone adjust (cast t to int to compare to negative one) */
+ if(-1 != (int)t) {
+
+ /* Add the time zone diff between local time zone and GMT. */
+ long delta = (long)(tzoff!=-1?tzoff:0);
+
+ if((delta>0) && (t > LONG_MAX - delta))
+ return -1; /* time_t overflow */
+
+ t += delta;
+ }
+
+ *output = t;
+
+ return PARSEDATE_OK;
+}
+
+time_t parse_date(const char *p)
+{
+ time_t parsed;
+ int rc = parsedate(p, &parsed);
+
+ switch(rc) {
+ case PARSEDATE_OK:
+ case PARSEDATE_LATER:
+ case PARSEDATE_SOONER:
+ return parsed;
+ }
+ /* everything else is fail */
+ return -1;
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/util/sqlite3.cpp b/src/util/sqlite3.cpp
new file mode 100644
index 0000000000..19e0ce9c79
--- /dev/null
+++ b/src/util/sqlite3.cpp
@@ -0,0 +1,166 @@
+#include <mbgl/util/sqlite3.hpp>
+#include <sqlite3.h>
+
+#include <cassert>
+
+namespace mapbox {
+namespace sqlite {
+
+Database::Database(const std::string &filename, int flags) {
+ const int err = sqlite3_open_v2(filename.c_str(), &db, flags, nullptr);
+ if (err != SQLITE_OK) {
+ Exception ex { err, sqlite3_errmsg(db) };
+ db = nullptr;
+ throw ex;
+ }
+}
+
+Database::Database(Database &&other) {
+ *this = std::move(other);
+}
+
+Database &Database::operator=(Database &&other) {
+ std::swap(db, other.db);
+ return *this;
+}
+
+Database::~Database() {
+ if (db) {
+ const int err = sqlite3_close(db);
+ if (err != SQLITE_OK) {
+ throw Exception { err, sqlite3_errmsg(db) };
+ }
+ }
+}
+
+Database::operator bool() const {
+ return db != nullptr;
+}
+
+void Database::exec(const std::string &sql) {
+ assert(db);
+ char *msg = nullptr;
+ const int err = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &msg);
+ if (msg) {
+ Exception ex { err, msg };
+ sqlite3_free(msg);
+ throw ex;
+ } else if (err != SQLITE_OK) {
+ throw Exception { err, sqlite3_errmsg(db) };
+ }
+}
+
+Statement Database::prepare(const char *query) {
+ assert(db);
+ return std::move(Statement(db, query));
+}
+
+Statement::Statement(sqlite3 *db, const char *sql) {
+ const int err = sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr);
+ if (err != SQLITE_OK) {
+ stmt = nullptr;
+ throw Exception { err, sqlite3_errmsg(db) };
+ }
+}
+
+#define CHECK_SQLITE_OK(err) \
+ if (err != SQLITE_OK) { \
+ throw Exception { err, sqlite3_errmsg(sqlite3_db_handle(stmt)) }; \
+ }
+
+Statement::Statement(Statement &&other) {
+ *this = std::move(other);
+}
+
+Statement &Statement::operator=(Statement &&other) {
+ std::swap(stmt, other.stmt);
+ return *this;
+}
+
+Statement::~Statement() {
+ if (stmt) {
+ const int err = sqlite3_finalize(stmt);
+ CHECK_SQLITE_OK(err)
+ }
+}
+
+Statement::operator bool() const {
+ return stmt != nullptr;
+}
+
+#define BIND_3(type, value) \
+ assert(stmt); \
+ const int err = sqlite3_bind_##type(stmt, offset, value); \
+ CHECK_SQLITE_OK(err)
+
+#define BIND_5(type, value, length, param) \
+ assert(stmt); \
+ const int err = sqlite3_bind_##type(stmt, offset, value, length, param); \
+ CHECK_SQLITE_OK(err)
+
+template <> void Statement::bind(int offset, int value) {
+ BIND_3(int, value)
+}
+
+template <> void Statement::bind(int offset, int64_t value) {
+ BIND_3(int64, value)
+}
+
+template <> void Statement::bind(int offset, double value) {
+ BIND_3(double, value)
+}
+
+template <> void Statement::bind(int offset, bool value) {
+ BIND_3(int, value)
+}
+
+template <> void Statement::bind(int offset, const char *value) {
+ BIND_5(text, value, -1, nullptr)
+}
+
+void Statement::bind(int offset, const std::string &value, bool retain) {
+ BIND_5(blob, value.data(), int(value.size()), retain ? SQLITE_TRANSIENT : SQLITE_STATIC)
+}
+
+bool Statement::run() {
+ assert(stmt);
+ const int err = sqlite3_step(stmt);
+ if (err == SQLITE_DONE) {
+ return false;
+ } else if (err == SQLITE_ROW) {
+ return true;
+ } else {
+ throw std::runtime_error("failed to run statement");
+ }
+}
+
+template <> int Statement::get(int offset) {
+ assert(stmt);
+ return sqlite3_column_int(stmt, offset);
+}
+
+template <> int64_t Statement::get(int offset) {
+ assert(stmt);
+ return sqlite3_column_int64(stmt, offset);
+}
+
+template <> double Statement::get(int offset) {
+ assert(stmt);
+ return sqlite3_column_double(stmt, offset);
+}
+
+template <> std::string Statement::get(int offset) {
+ assert(stmt);
+ return {
+ reinterpret_cast<const char *>(sqlite3_column_blob(stmt, offset)),
+ size_t(sqlite3_column_bytes(stmt, offset))
+ };
+}
+
+void Statement::reset() {
+ assert(stmt);
+ sqlite3_reset(stmt);
+}
+
+}
+}
diff --git a/src/util/uv-channel.c b/src/util/uv-channel.c
new file mode 100644
index 0000000000..3d0483fd70
--- /dev/null
+++ b/src/util/uv-channel.c
@@ -0,0 +1,55 @@
+#include <mbgl/util/uv-channel.h>
+#include <mbgl/util/queue.h>
+
+#include <stdlib.h>
+
+// Taken from http://navaneeth.github.io/blog/2013/08/02/channels-in-libuv/
+
+typedef struct {
+ void *data;
+ void *active_queue[2];
+} uv__chan_item_t;
+
+int uv_chan_init(uv_chan_t *chan) {
+ int r = uv_mutex_init(&chan->mutex);
+ if (r == -1)
+ return r;
+
+ QUEUE_INIT(&chan->q);
+
+ return uv_cond_init(&chan->cond);
+}
+
+void uv_chan_send(uv_chan_t *chan, void *data) {
+ uv__chan_item_t *item = malloc(sizeof(uv__chan_item_t));
+ item->data = data;
+
+ uv_mutex_lock(&chan->mutex);
+ QUEUE_INSERT_TAIL(&chan->q, &item->active_queue);
+ uv_cond_signal(&chan->cond);
+ uv_mutex_unlock(&chan->mutex);
+}
+
+void *uv_chan_receive(uv_chan_t *chan) {
+ uv__chan_item_t *item;
+ QUEUE *head;
+ void *data = NULL;
+
+ uv_mutex_lock(&chan->mutex);
+ while (QUEUE_EMPTY(&chan->q)) {
+ uv_cond_wait(&chan->cond, &chan->mutex);
+ }
+
+ head = QUEUE_HEAD(&chan->q);
+ item = QUEUE_DATA(head, uv__chan_item_t, active_queue);
+ data = item->data;
+ QUEUE_REMOVE(head);
+ free(item);
+ uv_mutex_unlock(&chan->mutex);
+ return data;
+}
+
+void uv_chan_destroy(uv_chan_t *chan) {
+ uv_cond_destroy(&chan->cond);
+ uv_mutex_destroy(&chan->mutex);
+}
diff --git a/src/util/uv-messenger.c b/src/util/uv-messenger.c
new file mode 100644
index 0000000000..e121439aa9
--- /dev/null
+++ b/src/util/uv-messenger.c
@@ -0,0 +1,70 @@
+#include <mbgl/util/uv-messenger.h>
+#include <mbgl/util/queue.h>
+
+#include <stdlib.h>
+
+typedef struct {
+ void *data;
+ void *queue[2];
+} uv__messenger_item_t;
+
+void uv__messenger_callback(uv_async_t *async) {
+ uv_messenger_t *msgr = (uv_messenger_t *)async->data;
+
+ uv__messenger_item_t *item;
+ QUEUE *head;
+
+ while (1) {
+ uv_mutex_lock(&msgr->mutex);
+ if (QUEUE_EMPTY(&msgr->queue)) {
+ uv_mutex_unlock(&msgr->mutex);
+ break;
+ }
+
+ head = QUEUE_HEAD(&msgr->queue);
+ item = QUEUE_DATA(head, uv__messenger_item_t, queue);
+ QUEUE_REMOVE(head);
+ uv_mutex_unlock(&msgr->mutex);
+
+ msgr->callback(item->data);
+
+ free(item);
+ }
+}
+
+int uv_messenger_init(uv_loop_t *loop, uv_messenger_t *msgr, uv_messenger_cb callback) {
+ int ret = uv_mutex_init(&msgr->mutex);
+ if (ret < 0) {
+ return ret;
+ }
+
+ msgr->callback = callback;
+
+ QUEUE_INIT(&msgr->queue);
+
+ msgr->async.data = msgr;
+ return uv_async_init(loop, &msgr->async, uv__messenger_callback);
+}
+
+void uv_messenger_send(uv_messenger_t *msgr, void *data) {
+ uv__messenger_item_t *item = malloc(sizeof(uv__messenger_item_t));
+ item->data = data;
+
+ uv_mutex_lock(&msgr->mutex);
+ QUEUE_INSERT_TAIL(&msgr->queue, &item->queue);
+ uv_mutex_unlock(&msgr->mutex);
+
+ uv_async_send(&msgr->async);
+}
+
+void uv_messenger_unref(uv_messenger_t *msgr) {
+ uv_unref((uv_handle_t *)&msgr->async);
+}
+
+void uv__messenger_stop_callback(uv_handle_t *handle) {
+ free((uv_messenger_t *)handle->data);
+}
+
+void uv_messenger_stop(uv_messenger_t *msgr) {
+ uv_close((uv_handle_t *)&msgr->async, uv__messenger_stop_callback);
+}
diff --git a/src/util/uv-worker.c b/src/util/uv-worker.c
new file mode 100644
index 0000000000..d0e2550b73
--- /dev/null
+++ b/src/util/uv-worker.c
@@ -0,0 +1,62 @@
+#include <mbgl/util/uv-worker.h>
+#include <mbgl/util/uv-messenger.h>
+
+#include <stdio.h>
+
+typedef struct uv__worker_item_s uv__worker_item_t;
+struct uv__worker_item_s {
+ void *data;
+ uv_worker_cb work_cb;
+ uv_worker_after_cb after_work_cb;
+};
+
+void uv__worker_after(void *ptr) {
+ uv__worker_item_t *item = (uv__worker_item_t *)ptr;
+ item->after_work_cb(item->data);
+ free(item);
+}
+
+void uv__worker_thread_loop(void *ptr) {
+ uv_worker_t *worker = (uv_worker_t *)ptr;
+
+#ifdef __APPLE__
+ if (worker->name) {
+ pthread_setname_np(worker->name);
+ }
+#endif
+
+ uv__worker_item_t *item = NULL;
+ while ((item = uv_chan_receive(&worker->chan)) != NULL) {
+ item->work_cb(item->data);
+ uv_messenger_send(worker->msgr, item);
+ }
+}
+
+int uv_worker_init(uv_worker_t *worker, uv_loop_t *loop) {
+ return uv_worker_init_named(worker, loop, NULL);
+}
+
+int uv_worker_init_named(uv_worker_t *worker, uv_loop_t *loop, const char *name) {
+ worker->name = name;
+ worker->msgr = malloc(sizeof(uv_messenger_t));
+ int ret = uv_messenger_init(loop, worker->msgr, uv__worker_after);
+ if (ret < 0) return ret;
+ ret = uv_chan_init(&worker->chan);
+ if (ret < 0) return ret;
+ return uv_thread_create(&worker->thread, uv__worker_thread_loop, worker);
+}
+
+void uv_worker_send(uv_worker_t *worker, void *data, uv_worker_cb work_cb,
+ uv_worker_after_cb after_work_cb) {
+ uv__worker_item_t *item = malloc(sizeof(uv__worker_item_t));
+ item->work_cb = work_cb;
+ item->after_work_cb = after_work_cb;
+ item->data = data;
+ uv_chan_send(&worker->chan, item);
+}
+
+void uv_worker_close(uv_worker_t *worker) {
+ uv_chan_send(&worker->chan, NULL);
+ uv_thread_join(&worker->thread);
+ uv_messenger_stop(worker->msgr);
+}