diff options
author | Konstantin Käfer <mail@kkaefer.com> | 2014-09-15 17:26:44 +0200 |
---|---|---|
committer | Konstantin Käfer <mail@kkaefer.com> | 2014-09-24 16:14:09 +0200 |
commit | d9fc7708a2dfb6e2506a5d10d896a813557c056d (patch) | |
tree | 50b2dba9e0a8766c88f7c276a8f71742a06a1d67 | |
parent | 062e911c6d570a794431023f9f0cb0b02cd85667 (diff) | |
download | qtlocation-mapboxgl-d9fc7708a2dfb6e2506a5d10d896a813557c056d.tar.gz |
do 304 requests and cache them in sqlite
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); +} |