diff options
author | Sudarsana Babu Nagineni <sudarsana.babu@mapbox.com> | 2018-07-30 15:30:57 +0300 |
---|---|---|
committer | Sudarsana Babu Nagineni <sudarsana.babu@mapbox.com> | 2018-08-06 16:55:30 +0300 |
commit | 0130c58fdd74edbbfe668f3125a9377554d7d605 (patch) | |
tree | 6be1c1d25d146e9273f04f40f2283e18048c17ac /platform | |
parent | 7373abef92ed4911b91f9fca3d97784ed0d9e02e (diff) | |
download | qtlocation-mapboxgl-0130c58fdd74edbbfe668f3125a9377554d7d605.tar.gz |
Bump Mapbox GL Native
Bump version.
mapbox-gl-native @ 377a6e42d687c419e6ae1012b8626336f5dfc1b6
Diffstat (limited to 'platform')
26 files changed, 914 insertions, 365 deletions
diff --git a/platform/default/collator.cpp b/platform/default/collator.cpp new file mode 100644 index 0000000000..b7f256756e --- /dev/null +++ b/platform/default/collator.cpp @@ -0,0 +1,79 @@ +#include <mbgl/style/expression/collator.hpp> +#include <mbgl/util/platform.hpp> +#include <libnu/strcoll.h> +#include <unaccent.hpp> + +/* + The default implementation of Collator ignores locale. + Case sensitivity and collation order are based on + Default Unicode Collation Element Table (DUCET). + + Diacritic-insensitivity is implemented with nunicode's + non-standard "unaccent" functionality, which is tailored + to European languages. + + It would be possible to implement locale awareness using ICU, + but would require bundling locale data. +*/ + +namespace mbgl { +namespace style { +namespace expression { + +class Collator::Impl { +public: + Impl(bool caseSensitive_, bool diacriticSensitive_, optional<std::string>) + : caseSensitive(caseSensitive_) + , diacriticSensitive(diacriticSensitive_) + {} + + bool operator==(const Impl& other) const { + return caseSensitive == other.caseSensitive && + diacriticSensitive == other.diacriticSensitive; + } + + int compare(const std::string& lhs, const std::string& rhs) const { + if (caseSensitive && diacriticSensitive) { + return nu_strcoll(lhs.c_str(), rhs.c_str(), + nu_utf8_read, nu_utf8_read); + } else if (!caseSensitive && diacriticSensitive) { + return nu_strcasecoll(lhs.c_str(), rhs.c_str(), + nu_utf8_read, nu_utf8_read); + } else if (caseSensitive && !diacriticSensitive) { + return nu_strcoll(platform::unaccent(lhs).c_str(), platform::unaccent(rhs).c_str(), + nu_utf8_read, nu_utf8_read); + } else { + return nu_strcasecoll(platform::unaccent(lhs).c_str(), platform::unaccent(rhs).c_str(), + nu_utf8_read, nu_utf8_read); + } + } + + std::string resolvedLocale() const { + return ""; + } +private: + bool caseSensitive; + bool diacriticSensitive; +}; + + +Collator::Collator(bool caseSensitive, bool diacriticSensitive, optional<std::string> locale_) + : impl(std::make_shared<Impl>(caseSensitive, diacriticSensitive, std::move(locale_))) +{} + +bool Collator::operator==(const Collator& other) const { + return *impl == *(other.impl); +} + +int Collator::compare(const std::string& lhs, const std::string& rhs) const { + return impl->compare(lhs, rhs); +} + +std::string Collator::resolvedLocale() const { + return impl->resolvedLocale(); +} + + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/platform/default/default_file_source.cpp b/platform/default/default_file_source.cpp index cb602995a4..f070121497 100644 --- a/platform/default/default_file_source.cpp +++ b/platform/default/default_file_source.cpp @@ -11,6 +11,7 @@ #include <mbgl/util/url.hpp> #include <mbgl/util/thread.hpp> #include <mbgl/util/work_request.hpp> +#include <mbgl/util/stopwatch.hpp> #include <cassert> @@ -18,15 +19,10 @@ namespace mbgl { class DefaultFileSource::Impl { public: - Impl(ActorRef<Impl> self, std::shared_ptr<FileSource> assetFileSource_, const std::string& cachePath, uint64_t maximumCacheSize) + Impl(std::shared_ptr<FileSource> assetFileSource_, std::string cachePath, uint64_t maximumCacheSize) : assetFileSource(assetFileSource_) - , localFileSource(std::make_unique<LocalFileSource>()) { - // Initialize the Database asynchronously so as to not block Actor creation. - self.invoke(&Impl::initializeOfflineDatabase, cachePath, maximumCacheSize); - } - - void initializeOfflineDatabase(std::string cachePath, uint64_t maximumCacheSize) { - offlineDatabase = std::make_unique<OfflineDatabase>(cachePath, maximumCacheSize); + , localFileSource(std::make_unique<LocalFileSource>()) + , offlineDatabase(std::make_unique<OfflineDatabase>(cachePath, maximumCacheSize)) { } void setAPIBaseURL(const std::string& url) { @@ -151,8 +147,17 @@ public: // Get from the online file source if (resource.hasLoadingMethod(Resource::LoadingMethod::Network)) { + MBGL_TIMING_START(watch); tasks[req] = onlineFileSource.request(resource, [=] (Response onlineResponse) mutable { this->offlineDatabase->put(resource, onlineResponse); + if (resource.kind == Resource::Kind::Tile) { + // onlineResponse.data will be null if data not modified + MBGL_TIMING_FINISH(watch, + " Action: " << "Requesting," << + " URL: " << resource.url.c_str() << + " Size: " << (onlineResponse.data != nullptr ? onlineResponse.data->size() : 0) << "B," << + " Time") + } callback(onlineResponse); }); } diff --git a/platform/default/http_file_source.cpp b/platform/default/http_file_source.cpp index a9c442c2de..9166d92fb7 100644 --- a/platform/default/http_file_source.cpp +++ b/platform/default/http_file_source.cpp @@ -13,6 +13,85 @@ #include <curl/curl.h> +// Dynamically load all cURL functions. Debian-derived systems upgraded the OpenSSL version linked +// to in https://salsa.debian.org/debian/curl/commit/95c94957bb7e89e36e78b995fed468c42f64d18d +// They state: +// Rename libcurl3 to libcurl4, because libcurl exposes an SSL_CTX via +// CURLOPT_SSL_CTX_FUNCTION, and this object changes incompatibly between +// openssl 1.0 and openssl 1.1. +// Since we are not accessing the underlying OpenSSL context, we don't care whether we're linking +// against libcurl3 or libcurl4; both use the ABI version 4 which hasn't changed since 2006 +// (see https://curl.haxx.se/libcurl/abi.html). In fact, cURL's ABI compatibility is very good as +// shown on https://abi-laboratory.pro/tracker/timeline/curl/ +// Therefore, we're dynamically loading the cURL symbols we need to avoid linking against versioned +// symbols. +#include <dlfcn.h> + +namespace curl { + +#define CURL_FUNCTIONS \ + X(global_init) \ + X(getdate) \ + X(easy_strerror) \ + X(easy_init) \ + X(easy_setopt) \ + X(easy_cleanup) \ + X(easy_getinfo) \ + X(easy_reset) \ + X(multi_init) \ + X(multi_add_handle) \ + X(multi_remove_handle) \ + X(multi_cleanup) \ + X(multi_info_read) \ + X(multi_strerror) \ + X(multi_socket_action) \ + X(multi_setopt) \ + X(share_init) \ + X(share_cleanup) \ + X(slist_append) \ + X(slist_free_all) + +#define X(name) static decltype(&curl_ ## name) name = nullptr; +CURL_FUNCTIONS +#undef X + +static void* handle = nullptr; + +static void* load(const char* name) { + void* symbol = dlsym(handle, name); + if (const char* error = dlerror()) { + fprintf(stderr, "Cannot load symbol '%s': %s\n", name, error); + dlclose(handle); + handle = nullptr; + abort(); + } + return symbol; +} + +__attribute__((constructor)) +static void load() { + assert(!handle); + handle = dlopen("libcurl.so.4", RTLD_LAZY | RTLD_LOCAL); + if (!handle) { + fprintf(stderr, "Could not open shared library '%s'\n", "libcurl.so.4"); + abort(); + } + + #define X(name) name = (decltype(&curl_ ## name))load("curl_" #name); + CURL_FUNCTIONS + #undef X +} + +__attribute__((constructor)) +static void unload() { + if (handle) { + dlclose(handle); + } +} + +} // namespace curl + + #include <queue> #include <map> #include <cassert> @@ -21,13 +100,13 @@ static void handleError(CURLMcode code) { if (code != CURLM_OK) { - throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(code)); + throw std::runtime_error(std::string("CURL multi error: ") + curl::multi_strerror(code)); } } static void handleError(CURLcode code) { if (code != CURLE_OK) { - throw std::runtime_error(std::string("CURL easy error: ") + curl_easy_strerror(code)); + throw std::runtime_error(std::string("CURL easy error: ") + curl::easy_strerror(code)); } } @@ -91,29 +170,29 @@ private: }; HTTPFileSource::Impl::Impl() { - if (curl_global_init(CURL_GLOBAL_ALL)) { + if (curl::global_init(CURL_GLOBAL_ALL)) { throw std::runtime_error("Could not init cURL"); } - share = curl_share_init(); + share = curl::share_init(); - multi = curl_multi_init(); - handleError(curl_multi_setopt(multi, CURLMOPT_SOCKETFUNCTION, handleSocket)); - handleError(curl_multi_setopt(multi, CURLMOPT_SOCKETDATA, this)); - handleError(curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, startTimeout)); - handleError(curl_multi_setopt(multi, CURLMOPT_TIMERDATA, this)); + multi = curl::multi_init(); + handleError(curl::multi_setopt(multi, CURLMOPT_SOCKETFUNCTION, handleSocket)); + handleError(curl::multi_setopt(multi, CURLMOPT_SOCKETDATA, this)); + handleError(curl::multi_setopt(multi, CURLMOPT_TIMERFUNCTION, startTimeout)); + handleError(curl::multi_setopt(multi, CURLMOPT_TIMERDATA, this)); } HTTPFileSource::Impl::~Impl() { while (!handles.empty()) { - curl_easy_cleanup(handles.front()); + curl::easy_cleanup(handles.front()); handles.pop(); } - curl_multi_cleanup(multi); + curl::multi_cleanup(multi); multi = nullptr; - curl_share_cleanup(share); + curl::share_cleanup(share); share = nullptr; timeout.stop(); @@ -125,12 +204,12 @@ CURL *HTTPFileSource::Impl::getHandle() { handles.pop(); return handle; } else { - return curl_easy_init(); + return curl::easy_init(); } } void HTTPFileSource::Impl::returnHandle(CURL *handle) { - curl_easy_reset(handle); + curl::easy_reset(handle); handles.push(handle); } @@ -138,11 +217,11 @@ void HTTPFileSource::Impl::checkMultiInfo() { CURLMsg *message = nullptr; int pending = 0; - while ((message = curl_multi_info_read(multi, &pending))) { + while ((message = curl::multi_info_read(multi, &pending))) { switch (message->msg) { case CURLMSG_DONE: { HTTPRequest *baton = nullptr; - curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&baton); + curl::easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&baton); assert(baton); baton->handleResult(message->data.result); } break; @@ -166,7 +245,7 @@ void HTTPFileSource::Impl::perform(curl_socket_t s, util::RunLoop::Event events) int running_handles = 0; - curl_multi_socket_action(multi, s, flags, &running_handles); + curl::multi_socket_action(multi, s, flags, &running_handles); checkMultiInfo(); } @@ -200,9 +279,9 @@ int HTTPFileSource::Impl::handleSocket(CURL * /* handle */, curl_socket_t s, int void HTTPFileSource::Impl::onTimeout(Impl *context) { int running_handles; - CURLMcode error = curl_multi_socket_action(context->multi, CURL_SOCKET_TIMEOUT, 0, &running_handles); + CURLMcode error = curl::multi_socket_action(context->multi, CURL_SOCKET_TIMEOUT, 0, &running_handles); if (error != CURLM_OK) { - throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(error)); + throw std::runtime_error(std::string("CURL multi error: ") + curl::multi_strerror(error)); } context->checkMultiInfo(); } @@ -233,45 +312,45 @@ HTTPRequest::HTTPRequest(HTTPFileSource::Impl* context_, Resource resource_, Fil // getting a 304 response if possible. This avoids redownloading unchanged data. if (resource.priorEtag) { const std::string header = std::string("If-None-Match: ") + *resource.priorEtag; - headers = curl_slist_append(headers, header.c_str()); + headers = curl::slist_append(headers, header.c_str()); } else if (resource.priorModified) { const std::string time = std::string("If-Modified-Since: ") + util::rfc1123(*resource.priorModified); - headers = curl_slist_append(headers, time.c_str()); + headers = curl::slist_append(headers, time.c_str()); } if (headers) { - curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers); + curl::easy_setopt(handle, CURLOPT_HTTPHEADER, headers); } - handleError(curl_easy_setopt(handle, CURLOPT_PRIVATE, this)); - handleError(curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, error)); - handleError(curl_easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt")); - handleError(curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1)); - handleError(curl_easy_setopt(handle, CURLOPT_URL, resource.url.c_str())); - handleError(curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, writeCallback)); - handleError(curl_easy_setopt(handle, CURLOPT_WRITEDATA, this)); - handleError(curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, headerCallback)); - handleError(curl_easy_setopt(handle, CURLOPT_HEADERDATA, this)); + handleError(curl::easy_setopt(handle, CURLOPT_PRIVATE, this)); + handleError(curl::easy_setopt(handle, CURLOPT_ERRORBUFFER, error)); + handleError(curl::easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt")); + handleError(curl::easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1)); + handleError(curl::easy_setopt(handle, CURLOPT_URL, resource.url.c_str())); + handleError(curl::easy_setopt(handle, CURLOPT_WRITEFUNCTION, writeCallback)); + handleError(curl::easy_setopt(handle, CURLOPT_WRITEDATA, this)); + handleError(curl::easy_setopt(handle, CURLOPT_HEADERFUNCTION, headerCallback)); + handleError(curl::easy_setopt(handle, CURLOPT_HEADERDATA, this)); #if LIBCURL_VERSION_NUM >= ((7) << 16 | (21) << 8 | 6) // Renamed in 7.21.6 - handleError(curl_easy_setopt(handle, CURLOPT_ACCEPT_ENCODING, "gzip, deflate")); + handleError(curl::easy_setopt(handle, CURLOPT_ACCEPT_ENCODING, "gzip, deflate")); #else - handleError(curl_easy_setopt(handle, CURLOPT_ENCODING, "gzip, deflate")); + handleError(curl::easy_setopt(handle, CURLOPT_ENCODING, "gzip, deflate")); #endif - handleError(curl_easy_setopt(handle, CURLOPT_USERAGENT, "MapboxGL/1.0")); - handleError(curl_easy_setopt(handle, CURLOPT_SHARE, context->share)); + handleError(curl::easy_setopt(handle, CURLOPT_USERAGENT, "MapboxGL/1.0")); + handleError(curl::easy_setopt(handle, CURLOPT_SHARE, context->share)); // Start requesting the information. - handleError(curl_multi_add_handle(context->multi, handle)); + handleError(curl::multi_add_handle(context->multi, handle)); } HTTPRequest::~HTTPRequest() { - handleError(curl_multi_remove_handle(context->multi, handle)); + handleError(curl::multi_remove_handle(context->multi, handle)); context->returnHandle(handle); handle = nullptr; if (headers) { - curl_slist_free_all(headers); + curl::slist_free_all(headers); headers = nullptr; } } @@ -320,7 +399,7 @@ size_t HTTPRequest::headerCallback(char *const buffer, const size_t size, const // Always overwrite the modification date; We might already have a value here from the // Date header, but this one is more accurate. const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n - baton->response->modified = Timestamp{ Seconds(curl_getdate(value.c_str(), nullptr)) }; + baton->response->modified = Timestamp{ Seconds(curl::getdate(value.c_str(), nullptr)) }; } else if ((begin = headerMatches("etag: ", buffer, length)) != std::string::npos) { baton->response->etag = std::string(buffer + begin, length - begin - 2); // remove \r\n } else if ((begin = headerMatches("cache-control: ", buffer, length)) != std::string::npos) { @@ -330,7 +409,7 @@ size_t HTTPRequest::headerCallback(char *const buffer, const size_t size, const baton->response->mustRevalidate = cc.mustRevalidate; } else if ((begin = headerMatches("expires: ", buffer, length)) != std::string::npos) { const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n - baton->response->expires = Timestamp{ Seconds(curl_getdate(value.c_str(), nullptr)) }; + baton->response->expires = Timestamp{ Seconds(curl::getdate(value.c_str(), nullptr)) }; } else if ((begin = headerMatches("retry-after: ", buffer, length)) != std::string::npos) { baton->retryAfter = std::string(buffer + begin, length - begin - 2); // remove \r\n } else if ((begin = headerMatches("x-rate-limit-reset: ", buffer, length)) != std::string::npos) { @@ -357,17 +436,17 @@ void HTTPRequest::handleResult(CURLcode code) { case CURLE_OPERATION_TIMEDOUT: response->error = std::make_unique<Error>( - Error::Reason::Connection, std::string{ curl_easy_strerror(code) } + ": " + error); + Error::Reason::Connection, std::string{ curl::easy_strerror(code) } + ": " + error); break; default: response->error = std::make_unique<Error>( - Error::Reason::Other, std::string{ curl_easy_strerror(code) } + ": " + error); + Error::Reason::Other, std::string{ curl::easy_strerror(code) } + ": " + error); break; } } else { long responseCode = 0; - curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &responseCode); + curl::easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &responseCode); if (responseCode == 200) { if (data) { diff --git a/platform/default/mbgl/map/map_snapshotter.cpp b/platform/default/mbgl/map/map_snapshotter.cpp index 9341c23cfd..565f72930d 100644 --- a/platform/default/mbgl/map/map_snapshotter.cpp +++ b/platform/default/mbgl/map/map_snapshotter.cpp @@ -13,18 +13,21 @@ namespace mbgl { class MapSnapshotter::Impl { public: - Impl(FileSource&, - Scheduler&, - const std::string& styleURL, + Impl(FileSource*, + std::shared_ptr<Scheduler>, + const std::pair<bool, std::string> style, const Size&, const float pixelRatio, - const CameraOptions&, + const optional<CameraOptions> cameraOptions, const optional<LatLngBounds> region, const optional<std::string> programCacheDir); void setStyleURL(std::string styleURL); std::string getStyleURL() const; + void setStyleJSON(std::string styleJSON); + std::string getStyleJSON() const; + void setSize(Size); Size getSize() const; @@ -37,24 +40,32 @@ public: void snapshot(ActorRef<MapSnapshotter::Callback>); private: + std::shared_ptr<Scheduler> scheduler; HeadlessFrontend frontend; Map map; }; -MapSnapshotter::Impl::Impl(FileSource& fileSource, - Scheduler& scheduler, - const std::string& styleURL, +MapSnapshotter::Impl::Impl(FileSource* fileSource, + std::shared_ptr<Scheduler> scheduler_, + const std::pair<bool, std::string> style, const Size& size, const float pixelRatio, - const CameraOptions& cameraOptions, + const optional<CameraOptions> cameraOptions, const optional<LatLngBounds> region, const optional<std::string> programCacheDir) - : frontend(size, pixelRatio, fileSource, scheduler, programCacheDir) - , map(frontend, MapObserver::nullObserver(), size, pixelRatio, fileSource, scheduler, MapMode::Static) { - - map.getStyle().loadURL(styleURL); + : scheduler(std::move(scheduler_)) + , frontend(size, pixelRatio, *fileSource, *scheduler, programCacheDir) + , map(frontend, MapObserver::nullObserver(), size, pixelRatio, *fileSource, *scheduler, MapMode::Static) { + + if (style.first) { + map.getStyle().loadJSON(style.second); + } else{ + map.getStyle().loadURL(style.second); + } - map.jumpTo(cameraOptions); + if (cameraOptions) { + map.jumpTo(*cameraOptions); + } // Set region, if specified if (region) { @@ -76,6 +87,15 @@ void MapSnapshotter::Impl::snapshot(ActorRef<MapSnapshotter::Callback> callback) return transform.latLngToScreenCoordinate(unwrappedLatLng); }}; + // Create lambda that captures the current transform state + // and can be used to translate for geographic to screen + // coordinates + assert (frontend.getTransformState()); + LatLngForFn latLngForFn { [=, transformState = *frontend.getTransformState()] (const ScreenCoordinate& screenCoordinate) { + Transform transform { transformState }; + return transform.screenCoordinateToLatLng(screenCoordinate); + }}; + // Collect all source attributions std::vector<std::string> attributions; for (auto source : map.getStyle().getSources()) { @@ -91,7 +111,8 @@ void MapSnapshotter::Impl::snapshot(ActorRef<MapSnapshotter::Callback> callback) error, error ? PremultipliedImage() : frontend.readStillImage(), std::move(attributions), - std::move(pointForFn) + std::move(pointForFn), + std::move(latLngForFn) ); }); } @@ -104,6 +125,14 @@ std::string MapSnapshotter::Impl::getStyleURL() const { return map.getStyle().getURL(); } +void MapSnapshotter::Impl::setStyleJSON(std::string styleJSON) { + map.getStyle().loadJSON(styleJSON); +} + +std::string MapSnapshotter::Impl::getStyleJSON() const { + return map.getStyle().getJSON(); +} + void MapSnapshotter::Impl::setSize(Size size) { map.setSize(size); frontend.setSize(size); @@ -132,15 +161,15 @@ LatLngBounds MapSnapshotter::Impl::getRegion() const { return map.latLngBoundsForCamera(getCameraOptions()); } -MapSnapshotter::MapSnapshotter(FileSource& fileSource, - Scheduler& scheduler, - const std::string& styleURL, +MapSnapshotter::MapSnapshotter(FileSource* fileSource, + std::shared_ptr<Scheduler> scheduler, + const std::pair<bool, std::string> style, const Size& size, const float pixelRatio, - const CameraOptions& cameraOptions, + const optional<CameraOptions> cameraOptions, const optional<LatLngBounds> region, const optional<std::string> programCacheDir) - : impl(std::make_unique<util::Thread<MapSnapshotter::Impl>>("Map Snapshotter", fileSource, scheduler, styleURL, size, pixelRatio, cameraOptions, region, programCacheDir)) { + : impl(std::make_unique<util::Thread<MapSnapshotter::Impl>>("Map Snapshotter", fileSource, std::move(scheduler), style, size, pixelRatio, cameraOptions, region, programCacheDir)) { } MapSnapshotter::~MapSnapshotter() = default; @@ -157,6 +186,14 @@ std::string MapSnapshotter::getStyleURL() const { return impl->actor().ask(&Impl::getStyleURL).get(); } +void MapSnapshotter::setStyleJSON(const std::string& styleJSON) { + impl->actor().invoke(&Impl::setStyleJSON, styleJSON); +} + +std::string MapSnapshotter::getStyleJSON() const { + return impl->actor().ask(&Impl::getStyleJSON).get(); +} + void MapSnapshotter::setSize(const Size& size) { impl->actor().invoke(&Impl::setSize, size); } diff --git a/platform/default/mbgl/map/map_snapshotter.hpp b/platform/default/mbgl/map/map_snapshotter.hpp index 985396e5a3..264f74541d 100644 --- a/platform/default/mbgl/map/map_snapshotter.hpp +++ b/platform/default/mbgl/map/map_snapshotter.hpp @@ -25,12 +25,12 @@ class Style; class MapSnapshotter { public: - MapSnapshotter(FileSource& fileSource, - Scheduler& scheduler, - const std::string& styleURL, + MapSnapshotter(FileSource* fileSource, + std::shared_ptr<Scheduler> scheduler, + const std::pair<bool, std::string> style, const Size&, const float pixelRatio, - const CameraOptions&, + const optional<CameraOptions> cameraOptions, const optional<LatLngBounds> region, const optional<std::string> cacheDir = {}); @@ -39,6 +39,9 @@ public: void setStyleURL(const std::string& styleURL); std::string getStyleURL() const; + void setStyleJSON(const std::string& styleJSON); + std::string getStyleJSON() const; + void setSize(const Size&); Size getSize() const; @@ -49,8 +52,9 @@ public: LatLngBounds getRegion() const; using PointForFn = std::function<ScreenCoordinate (const LatLng&)>; + using LatLngForFn = std::function<LatLng (const ScreenCoordinate&)>; using Attributions = std::vector<std::string>; - using Callback = std::function<void (std::exception_ptr, PremultipliedImage, Attributions, PointForFn)>; + using Callback = std::function<void (std::exception_ptr, PremultipliedImage, Attributions, PointForFn, LatLngForFn)>; void snapshot(ActorRef<Callback>); private: diff --git a/platform/default/mbgl/storage/.clang-tidy b/platform/default/mbgl/storage/.clang-tidy new file mode 100644 index 0000000000..b4ecd26b95 --- /dev/null +++ b/platform/default/mbgl/storage/.clang-tidy @@ -0,0 +1,2 @@ +Checks: 'modernize-*,misc-static-assert,llvm-namespace-comment,-clang-analyzer-security.insecureAPI.rand,-clang-analyzer-core.uninitialized.UndefReturn,-clang-analyzer-core.StackAddressEscape,-clang-analyzer-core.CallAndMessage,-clang-diagnostic-unused-command-line-argument,-clang-analyzer-core.uninitialized.*,-clang-analyzer-core.NullDereference,-clang-analyzer-cplusplus.NewDelete,-clang-analyzer-unix.MismatchedDeallocator,-clang-analyzer-unix.cstring.NullArg,-clang-analyzer-cplusplus.NewDeleteLeaks,-clang-analyzer-unix.Malloc,-clang-analyzer-core.NonNullParamChecker' +HeaderFilterRegex: '\/mbgl\/' diff --git a/platform/default/mbgl/storage/offline_database.cpp b/platform/default/mbgl/storage/offline_database.cpp index 4611e69f43..8f7f0965f4 100644 --- a/platform/default/mbgl/storage/offline_database.cpp +++ b/platform/default/mbgl/storage/offline_database.cpp @@ -6,6 +6,8 @@ #include <mbgl/util/chrono.hpp> #include <mbgl/util/logging.hpp> +#include "offline_schema.hpp" + #include "sqlite3.hpp" namespace mbgl { @@ -27,58 +29,73 @@ OfflineDatabase::~OfflineDatabase() { } } -void OfflineDatabase::connect(int flags) { - db = std::make_unique<mapbox::sqlite::Database>(path.c_str(), flags); - db->setBusyTimeout(Milliseconds::max()); - db->exec("PRAGMA foreign_keys = ON"); -} - void OfflineDatabase::ensureSchema() { - if (path != ":memory:") { - try { - connect(mapbox::sqlite::ReadWrite); - - switch (userVersion()) { - case 0: break; // cache-only database; ok to delete - case 1: break; // cache-only database; ok to delete - case 2: migrateToVersion3(); // fall through - case 3: // no-op and fall through - case 4: migrateToVersion5(); // fall through - case 5: migrateToVersion6(); // fall through - case 6: return; - default: break; // downgrade, delete the database - } - + auto result = mapbox::sqlite::Database::tryOpen(path, mapbox::sqlite::ReadWriteCreate); + if (result.is<mapbox::sqlite::Exception>()) { + const auto& ex = result.get<mapbox::sqlite::Exception>(); + if (ex.code == mapbox::sqlite::ResultCode::NotADB) { + // Corrupted; blow it away. removeExisting(); - connect(mapbox::sqlite::ReadWrite | mapbox::sqlite::Create); - } catch (mapbox::sqlite::Exception& ex) { - if (ex.code != mapbox::sqlite::ResultCode::CantOpen && ex.code != mapbox::sqlite::ResultCode::NotADB) { - Log::Error(Event::Database, "Unexpected error connecting to database: %s", ex.what()); - throw; - } - - try { - if (ex.code == mapbox::sqlite::ResultCode::NotADB) { - removeExisting(); - } - connect(mapbox::sqlite::ReadWrite | mapbox::sqlite::Create); - } catch (...) { - Log::Error(Event::Database, "Unexpected error creating database: %s", util::toString(std::current_exception()).c_str()); - throw; - } + result = mapbox::sqlite::Database::open(path, mapbox::sqlite::ReadWriteCreate); + } else { + Log::Error(Event::Database, "Unexpected error connecting to database: %s", ex.what()); + throw ex; } } try { - #include "offline_schema.cpp.include" + assert(result.is<mapbox::sqlite::Database>()); + db = std::make_unique<mapbox::sqlite::Database>(std::move(result.get<mapbox::sqlite::Database>())); + db->setBusyTimeout(Milliseconds::max()); + db->exec("PRAGMA foreign_keys = ON"); + + switch (userVersion()) { + case 0: + case 1: + // Newly created database, or old cache-only database; remove old table if it exists. + removeOldCacheTable(); + break; + case 2: + migrateToVersion3(); + // fall through + case 3: + case 4: + migrateToVersion5(); + // fall through + case 5: + migrateToVersion6(); + // fall through + case 6: + // happy path; we're done + return; + default: + // downgrade, delete the database + removeExisting(); + break; + } + } catch (const mapbox::sqlite::Exception& ex) { + // Unfortunately, SQLITE_NOTADB is not always reported upon opening the database. + // Apparently sometimes it is delayed until the first read operation. + if (ex.code == mapbox::sqlite::ResultCode::NotADB) { + removeExisting(); + } else { + throw; + } + } - connect(mapbox::sqlite::ReadWrite | mapbox::sqlite::Create); + try { + // When downgrading the database, or when the database is corrupt, we've deleted the old database handle, + // so we need to reopen it. + if (!db) { + db = std::make_unique<mapbox::sqlite::Database>(mapbox::sqlite::Database::open(path, mapbox::sqlite::ReadWriteCreate)); + db->setBusyTimeout(Milliseconds::max()); + db->exec("PRAGMA foreign_keys = ON"); + } - // If you change the schema you must write a migration from the previous version. db->exec("PRAGMA auto_vacuum = INCREMENTAL"); db->exec("PRAGMA journal_mode = DELETE"); db->exec("PRAGMA synchronous = FULL"); - db->exec(schema); + db->exec(offlineDatabaseSchema); db->exec("PRAGMA user_version = 6"); } catch (...) { Log::Error(Event::Database, "Unexpected error creating database schema: %s", util::toString(std::current_exception()).c_str()); @@ -93,6 +110,7 @@ int OfflineDatabase::userVersion() { void OfflineDatabase::removeExisting() { Log::Warning(Event::Database, "Removing existing incompatible offline database"); + statements.clear(); db.reset(); try { @@ -102,6 +120,11 @@ void OfflineDatabase::removeExisting() { } } +void OfflineDatabase::removeOldCacheTable() { + db->exec("DROP TABLE IF EXISTS http_cache"); + db->exec("VACUUM"); +} + void OfflineDatabase::migrateToVersion3() { db->exec("PRAGMA auto_vacuum = INCREMENTAL"); db->exec("VACUUM"); @@ -160,7 +183,10 @@ optional<int64_t> OfflineDatabase::hasInternal(const Resource& resource) { } std::pair<bool, uint64_t> OfflineDatabase::put(const Resource& resource, const Response& response) { - return putInternal(resource, response, true); + mapbox::sqlite::Transaction transaction(*db, mapbox::sqlite::Transaction::Immediate); + auto result = putInternal(resource, response, true); + transaction.commit(); + return result; } std::pair<bool, uint64_t> OfflineDatabase::putInternal(const Resource& resource, const Response& response, bool evict_) { @@ -179,7 +205,7 @@ std::pair<bool, uint64_t> OfflineDatabase::putInternal(const Resource& resource, } if (evict_ && !evict(size)) { - Log::Debug(Event::Database, "Unable to make space for entry"); + Log::Info(Event::Database, "Unable to make space for entry"); return { false, 0 }; } @@ -277,11 +303,6 @@ bool OfflineDatabase::putResource(const Resource& resource, } // We can't use REPLACE because it would change the id value. - - // Begin an immediate-mode transaction to ensure that two writers do not attempt - // to INSERT a resource at the same moment. - mapbox::sqlite::Transaction transaction(*db, mapbox::sqlite::Transaction::Immediate); - // clang-format off mapbox::sqlite::Query updateQuery{ getStatement( "UPDATE resources " @@ -314,7 +335,6 @@ bool OfflineDatabase::putResource(const Resource& resource, updateQuery.run(); if (updateQuery.changes() != 0) { - transaction.commit(); return false; } @@ -341,7 +361,6 @@ bool OfflineDatabase::putResource(const Resource& resource, } insertQuery.run(); - transaction.commit(); return true; } @@ -469,10 +488,6 @@ bool OfflineDatabase::putTile(const Resource::TileData& tile, // We can't use REPLACE because it would change the id value. - // Begin an immediate-mode transaction to ensure that two writers do not attempt - // to INSERT a resource at the same moment. - mapbox::sqlite::Transaction transaction(*db, mapbox::sqlite::Transaction::Immediate); - // clang-format off mapbox::sqlite::Query updateQuery{ getStatement( "UPDATE tiles " @@ -511,7 +526,6 @@ bool OfflineDatabase::putTile(const Resource::TileData& tile, updateQuery.run(); if (updateQuery.changes() != 0) { - transaction.commit(); return false; } @@ -541,7 +555,6 @@ bool OfflineDatabase::putTile(const Resource::TileData& tile, } insertQuery.run(); - transaction.commit(); return true; } @@ -624,6 +637,43 @@ optional<int64_t> OfflineDatabase::hasRegionResource(int64_t regionID, const Res } uint64_t OfflineDatabase::putRegionResource(int64_t regionID, const Resource& resource, const Response& response) { + mapbox::sqlite::Transaction transaction(*db); + auto size = putRegionResourceInternal(regionID, resource, response); + transaction.commit(); + return size; +} + +void OfflineDatabase::putRegionResources(int64_t regionID, const std::list<std::tuple<Resource, Response>>& resources, OfflineRegionStatus& status) { + mapbox::sqlite::Transaction transaction(*db); + + for (const auto& elem : resources) { + const auto& resource = std::get<0>(elem); + const auto& response = std::get<1>(elem); + + try { + uint64_t resourceSize = putRegionResourceInternal(regionID, resource, response); + status.completedResourceCount++; + status.completedResourceSize += resourceSize; + if (resource.kind == Resource::Kind::Tile) { + status.completedTileCount += 1; + status.completedTileSize += resourceSize; + } + } catch (const MapboxTileLimitExceededException&) { + // Commit the rest of the batch and retrow + transaction.commit(); + throw; + } + } + + // Commit the completed batch + transaction.commit(); +} + +uint64_t OfflineDatabase::putRegionResourceInternal(int64_t regionID, const Resource& resource, const Response& response) { + if (exceedsOfflineMapboxTileCountLimit(resource)) { + throw MapboxTileLimitExceededException(); + } + uint64_t size = putInternal(resource, response, false).second; bool previouslyUnused = markUsed(regionID, resource); @@ -894,4 +944,10 @@ uint64_t OfflineDatabase::getOfflineMapboxTileCount() { return *offlineMapboxTileCount; } +bool OfflineDatabase::exceedsOfflineMapboxTileCountLimit(const Resource& resource) { + return resource.kind == Resource::Kind::Tile + && util::mapbox::isMapboxURL(resource.url) + && offlineMapboxTileCountLimitExceeded(); +} + } // namespace mbgl diff --git a/platform/default/mbgl/storage/offline_database.hpp b/platform/default/mbgl/storage/offline_database.hpp index 9673ad8212..38eb3783ba 100644 --- a/platform/default/mbgl/storage/offline_database.hpp +++ b/platform/default/mbgl/storage/offline_database.hpp @@ -2,6 +2,7 @@ #include <mbgl/storage/resource.hpp> #include <mbgl/storage/offline.hpp> +#include <mbgl/util/exception.hpp> #include <mbgl/util/noncopyable.hpp> #include <mbgl/util/optional.hpp> #include <mbgl/util/constants.hpp> @@ -10,6 +11,7 @@ #include <unordered_map> #include <memory> #include <string> +#include <list> namespace mapbox { namespace sqlite { @@ -24,6 +26,10 @@ namespace mbgl { class Response; class TileID; +struct MapboxTileLimitExceededException : util::Exception { + MapboxTileLimitExceededException() : util::Exception("Mapbox tile limit exceeded") {} +}; + class OfflineDatabase : private util::noncopyable { public: // Limits affect ambient caching (put) only; resources required by offline @@ -49,6 +55,7 @@ public: optional<std::pair<Response, uint64_t>> getRegionResource(int64_t regionID, const Resource&); optional<int64_t> hasRegionResource(int64_t regionID, const Resource&); uint64_t putRegionResource(int64_t regionID, const Resource&, const Response&); + void putRegionResources(int64_t regionID, const std::list<std::tuple<Resource, Response>>&, OfflineRegionStatus&); OfflineRegionDefinition getRegionDefinition(int64_t regionID); OfflineRegionStatus getRegionCompletedStatus(int64_t regionID); @@ -57,12 +64,13 @@ public: uint64_t getOfflineMapboxTileCountLimit(); bool offlineMapboxTileCountLimitExceeded(); uint64_t getOfflineMapboxTileCount(); + bool exceedsOfflineMapboxTileCountLimit(const Resource&); private: - void connect(int flags); int userVersion(); void ensureSchema(); void removeExisting(); + void removeOldCacheTable(); void migrateToVersion3(); void migrateToVersion5(); void migrateToVersion6(); @@ -79,6 +87,8 @@ private: bool putResource(const Resource&, const Response&, const std::string&, bool compressed); + uint64_t putRegionResourceInternal(int64_t regionID, const Resource&, const Response&); + optional<std::pair<Response, uint64_t>> getInternal(const Resource&); optional<int64_t> hasInternal(const Resource&); std::pair<bool, uint64_t> putInternal(const Resource&, const Response&, bool evict); diff --git a/platform/default/mbgl/storage/offline_download.cpp b/platform/default/mbgl/storage/offline_download.cpp index ba504c1f9b..4da51db655 100644 --- a/platform/default/mbgl/storage/offline_download.cpp +++ b/platform/default/mbgl/storage/offline_download.cpp @@ -330,7 +330,8 @@ void OfflineDownload::ensureResource(const Resource& resource, return; } - if (checkTileCountLimit(resource)) { + if (offlineDatabase.exceedsOfflineMapboxTileCountLimit(resource)) { + onMapboxTileCountLimitExceeded(); return; } @@ -347,17 +348,24 @@ void OfflineDownload::ensureResource(const Resource& resource, callback(onlineResponse); } - status.completedResourceCount++; - uint64_t resourceSize = offlineDatabase.putRegionResource(id, resource, onlineResponse); - status.completedResourceSize += resourceSize; - if (resource.kind == Resource::Kind::Tile) { - status.completedTileCount += 1; - status.completedTileSize += resourceSize; - } + // Queue up for batched insertion + buffer.emplace_back(resource, onlineResponse); - observer->statusChanged(status); + // Flush buffer periodically + if (buffer.size() == 64 || resourcesRemaining.size() == 0) { + try { + offlineDatabase.putRegionResources(id, buffer, status); + } catch (const MapboxTileLimitExceededException&) { + onMapboxTileCountLimitExceeded(); + return; + } - if (checkTileCountLimit(resource)) { + buffer.clear(); + observer->statusChanged(status); + } + + if (offlineDatabase.exceedsOfflineMapboxTileCountLimit(resource)) { + onMapboxTileCountLimitExceeded(); return; } @@ -366,15 +374,9 @@ void OfflineDownload::ensureResource(const Resource& resource, }); } -bool OfflineDownload::checkTileCountLimit(const Resource& resource) { - if (resource.kind == Resource::Kind::Tile && util::mapbox::isMapboxURL(resource.url) && - offlineDatabase.offlineMapboxTileCountLimitExceeded()) { - observer->mapboxTileCountLimitExceeded(offlineDatabase.getOfflineMapboxTileCountLimit()); - setState(OfflineRegionDownloadState::Inactive); - return true; - } - - return false; +void OfflineDownload::onMapboxTileCountLimitExceeded() { + observer->mapboxTileCountLimitExceeded(offlineDatabase.getOfflineMapboxTileCountLimit()); + setState(OfflineRegionDownloadState::Inactive); } } // namespace mbgl diff --git a/platform/default/mbgl/storage/offline_download.hpp b/platform/default/mbgl/storage/offline_download.hpp index 437f221c11..cffac1665b 100644 --- a/platform/default/mbgl/storage/offline_download.hpp +++ b/platform/default/mbgl/storage/offline_download.hpp @@ -46,7 +46,8 @@ private: * is deactivated, all in progress requests are cancelled. */ void ensureResource(const Resource&, std::function<void (Response)> = {}); - bool checkTileCountLimit(const Resource& resource); + + void onMapboxTileCountLimitExceeded(); int64_t id; OfflineRegionDefinition definition; @@ -58,6 +59,7 @@ private: std::list<std::unique_ptr<AsyncRequest>> requests; std::unordered_set<std::string> requiredSourceURLs; std::deque<Resource> resourcesRemaining; + std::list<std::tuple<Resource, Response>> buffer; void queueResource(Resource); void queueTiles(style::SourceType, uint16_t tileSize, const Tileset&); diff --git a/platform/default/mbgl/storage/offline_schema.cpp.include b/platform/default/mbgl/storage/offline_schema.hpp index 41af81e55b..e177d0dbd3 100644 --- a/platform/default/mbgl/storage/offline_schema.cpp.include +++ b/platform/default/mbgl/storage/offline_schema.hpp @@ -1,5 +1,11 @@ -/* THIS IS A GENERATED FILE; EDIT offline_schema.sql INSTEAD */ -static const char * schema = +#pragma once + +// THIS IS A GENERATED FILE; EDIT offline_schema.sql INSTEAD +// To regenerate, run `node platform/default/mbgl/storage/offline_schema.js` + +namespace mbgl { + +static constexpr const char* offlineDatabaseSchema = "CREATE TABLE resources (\n" " id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n" " url TEXT NOT NULL,\n" @@ -53,3 +59,5 @@ static const char * schema = "CREATE INDEX region_tiles_tile_id\n" "ON region_tiles (tile_id);\n" ; + +} // namespace mbgl diff --git a/platform/default/mbgl/storage/offline_schema.js b/platform/default/mbgl/storage/offline_schema.js new file mode 100644 index 0000000000..fdb7dc6405 --- /dev/null +++ b/platform/default/mbgl/storage/offline_schema.js @@ -0,0 +1,21 @@ +var fs = require('fs'); +fs.writeFileSync('platform/default/mbgl/storage/offline_schema.hpp', `#pragma once + +// THIS IS A GENERATED FILE; EDIT offline_schema.sql INSTEAD +// To regenerate, run \`node platform/default/mbgl/storage/offline_schema.js\` + +namespace mbgl { + +static constexpr const char* offlineDatabaseSchema = +${fs.readFileSync('platform/default/mbgl/storage/offline_schema.sql', 'utf8') + .replace(/ *--.*/g, '') + .split('\n') + .filter(a => a) + .map(line => '"' + line + '\\n"') + .join('\n') +} +; + +} // namespace mbgl +`); + diff --git a/platform/default/mbgl/storage/offline_schema.sql b/platform/default/mbgl/storage/offline_schema.sql new file mode 100644 index 0000000000..722b0e0451 --- /dev/null +++ b/platform/default/mbgl/storage/offline_schema.sql @@ -0,0 +1,64 @@ +CREATE TABLE resources ( -- Generic table for style, source, sprite, and glyph resources. + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + url TEXT NOT NULL, + kind INTEGER NOT NULL, + expires INTEGER, + modified INTEGER, + etag TEXT, + data BLOB, + compressed INTEGER NOT NULL DEFAULT 0, + accessed INTEGER NOT NULL, + must_revalidate INTEGER NOT NULL DEFAULT 0, + UNIQUE (url) +); + +CREATE TABLE tiles ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + url_template TEXT NOT NULL, + pixel_ratio INTEGER NOT NULL, + z INTEGER NOT NULL, + x INTEGER NOT NULL, + y INTEGER NOT NULL, + expires INTEGER, + modified INTEGER, + etag TEXT, + data BLOB, + compressed INTEGER NOT NULL DEFAULT 0, + accessed INTEGER NOT NULL, + must_revalidate INTEGER NOT NULL DEFAULT 0, + UNIQUE (url_template, pixel_ratio, z, x, y) +); + +CREATE TABLE regions ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + definition TEXT NOT NULL, -- JSON formatted definition of region. Regions may be of variant types: + -- e.g. bbox and zoom range, route path, flyTo parameters, etc. Note that + -- the set of tiles required for a region may span multiple sources. + description BLOB -- User provided data in user-defined format +); + +CREATE TABLE region_resources ( + region_id INTEGER NOT NULL REFERENCES regions(id) ON DELETE CASCADE, + resource_id INTEGER NOT NULL REFERENCES resources(id), + UNIQUE (region_id, resource_id) +); + +CREATE TABLE region_tiles ( + region_id INTEGER NOT NULL REFERENCES regions(id) ON DELETE CASCADE, + tile_id INTEGER NOT NULL REFERENCES tiles(id), + UNIQUE (region_id, tile_id) +); + +-- Indexes for efficient eviction queries + +CREATE INDEX resources_accessed +ON resources (accessed); + +CREATE INDEX tiles_accessed +ON tiles (accessed); + +CREATE INDEX region_resources_resource_id +ON region_resources (resource_id); + +CREATE INDEX region_tiles_tile_id +ON region_tiles (tile_id); diff --git a/platform/default/sqlite3.cpp b/platform/default/sqlite3.cpp index 8f9c34191f..2e76f6eec4 100644 --- a/platform/default/sqlite3.cpp +++ b/platform/default/sqlite3.cpp @@ -14,27 +14,23 @@ namespace sqlite { class DatabaseImpl { public: - DatabaseImpl(const char* filename, int flags) + DatabaseImpl(sqlite3* db_) + : db(db_) { - const int error = sqlite3_open_v2(filename, &db, flags, nullptr); - if (error != SQLITE_OK) { - const auto message = sqlite3_errmsg(db); - db = nullptr; - throw Exception { error, message }; - } } ~DatabaseImpl() { - if (!db) return; - const int error = sqlite3_close(db); if (error != SQLITE_OK) { mbgl::Log::Error(mbgl::Event::Database, "%s (Code %i)", sqlite3_errmsg(db), error); } } - sqlite3* db = nullptr; + void setBusyTimeout(std::chrono::milliseconds timeout); + void exec(const std::string& sql); + + sqlite3* db; }; class StatementImpl { @@ -69,84 +65,8 @@ public: template <typename T> using optional = std::experimental::optional<T>; -static const char* codeToString(const int err) { - switch (err) { - case SQLITE_OK: return "SQLITE_OK"; - case SQLITE_ERROR: return "SQLITE_ERROR"; - case SQLITE_INTERNAL: return "SQLITE_INTERNAL"; - case SQLITE_PERM: return "SQLITE_PERM"; - case SQLITE_ABORT: return "SQLITE_ABORT"; - case SQLITE_BUSY: return "SQLITE_BUSY"; - case SQLITE_LOCKED: return "SQLITE_LOCKED"; - case SQLITE_NOMEM: return "SQLITE_NOMEM"; - case SQLITE_READONLY: return "SQLITE_READONLY"; - case SQLITE_INTERRUPT: return "SQLITE_INTERRUPT"; - case SQLITE_IOERR: return "SQLITE_IOERR"; - case SQLITE_CORRUPT: return "SQLITE_CORRUPT"; - case SQLITE_NOTFOUND: return "SQLITE_NOTFOUND"; - case SQLITE_FULL: return "SQLITE_FULL"; - case SQLITE_CANTOPEN: return "SQLITE_CANTOPEN"; - case SQLITE_PROTOCOL: return "SQLITE_PROTOCOL"; - case SQLITE_EMPTY: return "SQLITE_EMPTY"; - case SQLITE_SCHEMA: return "SQLITE_SCHEMA"; - case SQLITE_TOOBIG: return "SQLITE_TOOBIG"; - case SQLITE_CONSTRAINT: return "SQLITE_CONSTRAINT"; - case SQLITE_MISMATCH: return "SQLITE_MISMATCH"; - case SQLITE_MISUSE: return "SQLITE_MISUSE"; - case SQLITE_NOLFS: return "SQLITE_NOLFS"; - case SQLITE_AUTH: return "SQLITE_AUTH"; - case SQLITE_FORMAT: return "SQLITE_FORMAT"; - case SQLITE_RANGE: return "SQLITE_RANGE"; - case SQLITE_NOTADB: return "SQLITE_NOTADB"; - case SQLITE_NOTICE: return "SQLITE_NOTICE"; - case SQLITE_WARNING: return "SQLITE_WARNING"; - case SQLITE_ROW: return "SQLITE_ROW"; - case SQLITE_DONE: return "SQLITE_DONE"; - default: return "<unknown>"; - } -} - static void errorLogCallback(void *, const int err, const char *msg) { - auto severity = mbgl::EventSeverity::Info; - - switch (err) { - case SQLITE_ERROR: // Generic error - case SQLITE_INTERNAL: // Internal logic error in SQLite - case SQLITE_PERM: // Access permission denied - case SQLITE_ABORT: // Callback routine requested an abort - case SQLITE_BUSY: // The database file is locked - case SQLITE_LOCKED: // A table in the database is locked - case SQLITE_NOMEM: // A malloc() failed - case SQLITE_READONLY: // Attempt to write a readonly database - case SQLITE_INTERRUPT: // Operation terminated by sqlite3_interrupt( - case SQLITE_IOERR: // Some kind of disk I/O error occurred - case SQLITE_CORRUPT: // The database disk image is malformed - case SQLITE_NOTFOUND: // Unknown opcode in sqlite3_file_control() - case SQLITE_FULL: // Insertion failed because database is full - case SQLITE_CANTOPEN: // Unable to open the database file - case SQLITE_PROTOCOL: // Database lock protocol error - case SQLITE_EMPTY: // Internal use only - case SQLITE_SCHEMA: // The database schema changed - case SQLITE_TOOBIG: // String or BLOB exceeds size limit - case SQLITE_CONSTRAINT: // Abort due to constraint violation - case SQLITE_MISMATCH: // Data type mismatch - case SQLITE_MISUSE: // Library used incorrectly - case SQLITE_NOLFS: // Uses OS features not supported on host - case SQLITE_AUTH: // Authorization denied - case SQLITE_FORMAT: // Not used - case SQLITE_RANGE: // 2nd parameter to sqlite3_bind out of range - case SQLITE_NOTADB: // File opened that is not a database file - severity = mbgl::EventSeverity::Error; - break; - case SQLITE_WARNING: // Warnings from sqlite3_log() - severity = mbgl::EventSeverity::Warning; - break; - case SQLITE_NOTICE: // Notifications from sqlite3_log() - default: - break; - } - - mbgl::Log::Record(severity, mbgl::Event::Database, "%s (%s)", msg, codeToString(err)); + mbgl::Log::Record(mbgl::EventSeverity::Info, mbgl::Event::Database, err, "%s", msg); } const static bool sqliteVersionCheck __attribute__((unused)) = []() { @@ -164,11 +84,29 @@ const static bool sqliteVersionCheck __attribute__((unused)) = []() { return true; }(); -Database::Database(const std::string &filename, int flags) - : impl(std::make_unique<DatabaseImpl>(filename.c_str(), flags)) -{ +mapbox::util::variant<Database, Exception> Database::tryOpen(const std::string &filename, int flags) { + sqlite3* db = nullptr; + const int error = sqlite3_open_v2(filename.c_str(), &db, flags, nullptr); + if (error != SQLITE_OK) { + const auto message = sqlite3_errmsg(db); + return Exception { error, message }; + } + return Database(std::make_unique<DatabaseImpl>(db)); +} + +Database Database::open(const std::string &filename, int flags) { + auto result = tryOpen(filename, flags); + if (result.is<Exception>()) { + throw result.get<Exception>(); + } else { + return std::move(result.get<Database>()); + } } +Database::Database(std::unique_ptr<DatabaseImpl> impl_) + : impl(std::move(impl_)) +{} + Database::Database(Database &&other) : impl(std::move(other.impl)) {} @@ -181,23 +119,31 @@ Database::~Database() = default; void Database::setBusyTimeout(std::chrono::milliseconds timeout) { assert(impl); - const int err = sqlite3_busy_timeout(impl->db, + impl->setBusyTimeout(timeout); +} + +void DatabaseImpl::setBusyTimeout(std::chrono::milliseconds timeout) { + const int err = sqlite3_busy_timeout(db, int(std::min<std::chrono::milliseconds::rep>(timeout.count(), std::numeric_limits<int>::max()))); if (err != SQLITE_OK) { - throw Exception { err, sqlite3_errmsg(impl->db) }; + throw Exception { err, sqlite3_errmsg(db) }; } } void Database::exec(const std::string &sql) { assert(impl); + impl->exec(sql); +} + +void DatabaseImpl::exec(const std::string& sql) { char *msg = nullptr; - const int err = sqlite3_exec(impl->db, sql.c_str(), nullptr, nullptr, &msg); + const int err = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &msg); if (msg) { const std::string message = msg; sqlite3_free(msg); throw Exception { err, message }; } else if (err != SQLITE_OK) { - throw Exception { err, sqlite3_errmsg(impl->db) }; + throw Exception { err, sqlite3_errmsg(db) }; } } @@ -470,16 +416,16 @@ uint64_t Query::changes() const { } Transaction::Transaction(Database& db_, Mode mode) - : db(db_) { + : dbImpl(*db_.impl) { switch (mode) { case Deferred: - db.exec("BEGIN DEFERRED TRANSACTION"); + dbImpl.exec("BEGIN DEFERRED TRANSACTION"); break; case Immediate: - db.exec("BEGIN IMMEDIATE TRANSACTION"); + dbImpl.exec("BEGIN IMMEDIATE TRANSACTION"); break; case Exclusive: - db.exec("BEGIN EXCLUSIVE TRANSACTION"); + dbImpl.exec("BEGIN EXCLUSIVE TRANSACTION"); break; } } @@ -496,12 +442,12 @@ Transaction::~Transaction() { void Transaction::commit() { needRollback = false; - db.exec("COMMIT TRANSACTION"); + dbImpl.exec("COMMIT TRANSACTION"); } void Transaction::rollback() { needRollback = false; - db.exec("ROLLBACK TRANSACTION"); + dbImpl.exec("ROLLBACK TRANSACTION"); } } // namespace sqlite diff --git a/platform/default/sqlite3.hpp b/platform/default/sqlite3.hpp index 20d09b550c..612e92acc6 100644 --- a/platform/default/sqlite3.hpp +++ b/platform/default/sqlite3.hpp @@ -5,18 +5,14 @@ #include <stdexcept> #include <chrono> #include <memory> +#include <mapbox/variant.hpp> namespace mapbox { namespace sqlite { enum OpenFlag : int { - ReadOnly = 0x00000001, - ReadWrite = 0x00000002, - Create = 0x00000004, - NoMutex = 0x00008000, - FullMutex = 0x00010000, - SharedCache = 0x00020000, - PrivateCache = 0x00040000, + ReadOnly = 0b001, + ReadWriteCreate = 0b110, }; enum class ResultCode : int { @@ -68,14 +64,18 @@ class DatabaseImpl; class Statement; class StatementImpl; class Query; +class Transaction; class Database { private: + Database(std::unique_ptr<DatabaseImpl>); Database(const Database &) = delete; Database &operator=(const Database &) = delete; public: - Database(const std::string &filename, int flags = 0); + static mapbox::util::variant<Database, Exception> tryOpen(const std::string &filename, int flags = 0); + static Database open(const std::string &filename, int flags = 0); + Database(Database &&); ~Database(); Database &operator=(Database &&); @@ -87,6 +87,7 @@ private: std::unique_ptr<DatabaseImpl> impl; friend class Statement; + friend class Transaction; }; // A Statement object represents a prepared statement that can be run repeatedly run with a Query object. @@ -169,7 +170,7 @@ public: void rollback(); private: - Database& db; + DatabaseImpl& dbImpl; bool needRollback = true; }; diff --git a/platform/default/string_stdlib.cpp b/platform/default/string_stdlib.cpp index 2642e88aff..103444df1c 100644 --- a/platform/default/string_stdlib.cpp +++ b/platform/default/string_stdlib.cpp @@ -1,8 +1,5 @@ #include <mbgl/util/platform.hpp> -#define NU_WITH_TOUPPER -#define NU_WITH_TOLOWER -#define NU_WITH_UTF8_WRITER -#include <libnu/libnu.h> +#include <libnu/casemap.h> #include <cstring> #include <sstream> diff --git a/platform/default/unaccent.cpp b/platform/default/unaccent.cpp new file mode 100644 index 0000000000..faefb4b4cd --- /dev/null +++ b/platform/default/unaccent.cpp @@ -0,0 +1,43 @@ +#include <mbgl/util/platform.hpp> +#include <libnu/unaccent.h> +#include <unaccent.hpp> + +#include <cstring> +#include <sstream> + +namespace mbgl { namespace platform { + +std::string unaccent(const std::string& str) +{ + std::stringstream output; + char const *itr = str.c_str(), *nitr; + char const *end = itr + str.length(); + char lo[5] = { 0 }; + + for (; itr < end; itr = nitr) + { + uint32_t code_point = 0; + char const* buf = nullptr; + + nitr = _nu_tounaccent(itr, end, nu_utf8_read, &code_point, &buf, nullptr); + if (buf != nullptr) + { + do + { + buf = NU_CASEMAP_DECODING_FUNCTION(buf, &code_point); + if (code_point == 0) break; + output.write(lo, nu_utf8_write(code_point, lo) - lo); + } + while (code_point != 0); + } + else + { + output.write(itr, nitr - itr); + } + } + + return output.str(); +} + +} // namespace platform +} // namespace mbgl diff --git a/platform/default/unaccent.hpp b/platform/default/unaccent.hpp new file mode 100644 index 0000000000..85ac37a7de --- /dev/null +++ b/platform/default/unaccent.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include <string> + +namespace mbgl { +namespace platform { + +// Non-locale-aware diacritic folding based on nunicode +// Used as a fallback when locale-aware comparisons aren't available +std::string unaccent(const std::string &string); + +} // namespace platform +} // namespace mbgl diff --git a/platform/qt/include/qmapboxgl.hpp b/platform/qt/include/qmapboxgl.hpp index 79eb672d1f..a699b77cec 100644 --- a/platform/qt/include/qmapboxgl.hpp +++ b/platform/qt/include/qmapboxgl.hpp @@ -236,8 +236,10 @@ public: bool layerExists(const QString &id); void removeLayer(const QString &id); - void setFilter(const QString &layer, const QVariant &filter); + QList<QString> layerIds() const; + void setFilter(const QString &layer, const QVariant &filter); + QVariant getFilter(const QString &layer) const; // When rendering on a different thread, // should be called on the render thread. void createRenderer(); diff --git a/platform/qt/src/qmapboxgl.cpp b/platform/qt/src/qmapboxgl.cpp index 8c3355dc09..09479581bb 100644 --- a/platform/qt/src/qmapboxgl.cpp +++ b/platform/qt/src/qmapboxgl.cpp @@ -55,6 +55,10 @@ #include <QString> #include <QStringList> #include <QThreadStorage> +#include <QVariant> +#include <QVariantList> +#include <QVariantMap> +#include <QColor> #include <memory> @@ -703,7 +707,7 @@ double QMapboxGL::scale() const void QMapboxGL::setScale(double scale_, const QPointF ¢er) { - d_ptr->mapObj->setZoom(mbgl::util::log2(scale_), mbgl::ScreenCoordinate { center.x(), center.y() }); + d_ptr->mapObj->setZoom(::log2(scale_), mbgl::ScreenCoordinate { center.x(), center.y() }); } /*! @@ -1111,7 +1115,7 @@ void QMapboxGL::moveBy(const QPointF &offset) can be used for implementing a pinch gesture. */ void QMapboxGL::scaleBy(double scale_, const QPointF ¢er) { - d_ptr->mapObj->setZoom(d_ptr->mapObj->getZoom() + mbgl::util::log2(scale_), mbgl::ScreenCoordinate { center.x(), center.y() }); + d_ptr->mapObj->setZoom(d_ptr->mapObj->getZoom() + ::log2(scale_), mbgl::ScreenCoordinate { center.x(), center.y() }); } /*! @@ -1466,6 +1470,23 @@ void QMapboxGL::removeLayer(const QString& id) } /*! + List of all existing layer ids from the current style. +*/ +QList<QString> QMapboxGL::layerIds() const +{ + const auto &layers = d_ptr->mapObj->getStyle().getLayers(); + + QList<QString> layerIds; + layerIds.reserve(layers.size()); + + for (const mbgl::style::Layer *layer : layers) { + layerIds.append(QString::fromStdString(layer->getID())); + } + + return layerIds; +} + +/*! Adds the \a image with the identifier \a id that can be used later by a symbol layer. @@ -1492,7 +1513,7 @@ void QMapboxGL::removeImage(const QString &id) /*! Adds a \a filter to a style \a layer using the format described in the \l - {https://www.mapbox.com/mapbox-gl-style-spec/#types-filter}{Mapbox style specification}. + {https://www.mapbox.com/mapbox-gl-js/style-spec/#other-filter}{Mapbox style specification}. Given a layer \c marker from an arbitrary GeoJSON source containing features of type \b "Point" and \b "LineString", this example shows how to make sure the layer will only tag @@ -1500,14 +1521,14 @@ void QMapboxGL::removeImage(const QString &id) \code QVariantList filterExpression; - filterExpression.append("=="); - filterExpression.append("$type"); - filterExpression.append("Point"); + filterExpression.push_back(QLatin1String("==")); + filterExpression.push_back(QLatin1String("$type")); + filterExpression.push_back(QLatin1String("Point")); QVariantList filter; - filter.append(filterExpression); + filter.push_back(filterExpression); - map->setFilter("marker", filter); + map->setFilter(QLatin1String("marker"), filter); \endcode */ void QMapboxGL::setFilter(const QString& layer, const QVariant& filter) @@ -1555,6 +1576,77 @@ void QMapboxGL::setFilter(const QString& layer, const QVariant& filter) qWarning() << "Layer doesn't support filters"; } +QVariant QVariantFromValue(const mbgl::Value &value) { + return value.match( + [](const mbgl::NullValue) { + return QVariant(); + }, [](const bool value_) { + return QVariant(value_); + }, [](const float value_) { + return QVariant(value_); + }, [](const int64_t value_) { + return QVariant(static_cast<qlonglong>(value_)); + }, [](const double value_) { + return QVariant(value_); + }, [](const std::string &value_) { + return QVariant(value_.c_str()); + }, [](const mbgl::Color &value_) { + return QColor(value_.r, value_.g, value_.b, value_.a); + }, [&](const std::vector<mbgl::Value> &vector) { + QVariantList list; + list.reserve(vector.size()); + for (const auto &value_ : vector) { + list.push_back(QVariantFromValue(value_)); + } + return list; + }, [&](const std::unordered_map<std::string, mbgl::Value> &map) { + QVariantMap varMap; + for (auto &item : map) { + varMap.insert(item.first.c_str(), QVariantFromValue(item.second)); + } + return varMap; + }, [](const auto &) { + return QVariant(); + }); +} + +/*! + Returns the current \a expression-based filter value applied to a style + \layer, if any. + + Filter value types are described in the {https://www.mapbox.com/mapbox-gl-js/style-spec/#types}{Mapbox style specification}. +*/ +QVariant QMapboxGL::getFilter(const QString &layer) const { + using namespace mbgl::style; + using namespace mbgl::style::conversion; + + Layer* layer_ = d_ptr->mapObj->getStyle().getLayer(layer.toStdString()); + if (!layer_) { + qWarning() << "Layer not found:" << layer; + return QVariant(); + } + + Filter filter_; + + if (layer_->is<FillLayer>()) { + filter_ = layer_->as<FillLayer>()->getFilter(); + } else if (layer_->is<LineLayer>()) { + filter_ = layer_->as<LineLayer>()->getFilter(); + } else if (layer_->is<SymbolLayer>()) { + filter_ = layer_->as<SymbolLayer>()->getFilter(); + } else if (layer_->is<CircleLayer>()) { + filter_ = layer_->as<CircleLayer>()->getFilter(); + } else if (layer_->is<FillExtrusionLayer>()) { + filter_ = layer_->as<FillExtrusionLayer>()->getFilter(); + } else { + qWarning() << "Layer doesn't support filters"; + return QVariant(); + } + + auto serialized = filter_.serialize(); + return QVariantFromValue(serialized); +} + /*! Creates the infrastructure needed for rendering the map. It should be called before any call to render(). diff --git a/platform/qt/src/qmapboxgl_map_renderer.cpp b/platform/qt/src/qmapboxgl_map_renderer.cpp index 7a9d1f6f78..acc4194498 100644 --- a/platform/qt/src/qmapboxgl_map_renderer.cpp +++ b/platform/qt/src/qmapboxgl_map_renderer.cpp @@ -1,12 +1,47 @@ #include "qmapboxgl_map_renderer.hpp" +#include "qmapboxgl_scheduler.hpp" +#include <QThreadStorage> #include <QtGlobal> +static bool needsToForceScheduler() { + static QThreadStorage<bool> force; + + if (!force.hasLocalData()) { + force.setLocalData(mbgl::Scheduler::GetCurrent() == nullptr); + } + + return force.localData(); +}; + +static auto *getScheduler() { + static QThreadStorage<std::shared_ptr<QMapboxGLScheduler>> scheduler; + + if (!scheduler.hasLocalData()) { + scheduler.setLocalData(std::make_shared<QMapboxGLScheduler>()); + } + + return scheduler.localData().get(); +}; + QMapboxGLMapRenderer::QMapboxGLMapRenderer(qreal pixelRatio, mbgl::DefaultFileSource &fs, mbgl::ThreadPool &tp, QMapboxGLSettings::GLContextMode mode) : m_renderer(std::make_unique<mbgl::Renderer>(m_backend, pixelRatio, fs, tp, static_cast<mbgl::GLContextMode>(mode))) - , m_threadWithScheduler(Scheduler::GetCurrent() != nullptr) + , m_forceScheduler(needsToForceScheduler()) { + // If we don't have a Scheduler on this thread, which + // is usually the case for render threads, use a shared + // dummy scheduler that needs to be explicitly forced to + // process events. + if (m_forceScheduler) { + auto scheduler = getScheduler(); + + if (mbgl::Scheduler::GetCurrent() == nullptr) { + mbgl::Scheduler::SetCurrent(scheduler); + } + + connect(scheduler, SIGNAL(needsProcessing()), this, SIGNAL(needsRendering())); + } } QMapboxGLMapRenderer::~QMapboxGLMapRenderer() @@ -14,16 +49,6 @@ QMapboxGLMapRenderer::~QMapboxGLMapRenderer() MBGL_VERIFY_THREAD(tid); } -void QMapboxGLMapRenderer::schedule(std::weak_ptr<mbgl::Mailbox> mailbox) -{ - std::lock_guard<std::mutex> lock(m_taskQueueMutex); - m_taskQueue.push(mailbox); - - // Need to force the main thread to wake - // up this thread and process the events. - emit needsRendering(); -} - void QMapboxGLMapRenderer::updateParameters(std::shared_ptr<mbgl::UpdateParameters> newParameters) { std::lock_guard<std::mutex> lock(m_updateMutex); @@ -57,26 +82,10 @@ void QMapboxGLMapRenderer::render() // The OpenGL implementation automatically enables the OpenGL context for us. mbgl::BackendScope scope(m_backend, mbgl::BackendScope::ScopeType::Implicit); - // If we don't have a Scheduler on this thread, which - // is usually the case for render threads, use this - // object as scheduler. - if (!m_threadWithScheduler) { - Scheduler::SetCurrent(this); - } - m_renderer->render(*params); - if (!m_threadWithScheduler) { - std::queue<std::weak_ptr<mbgl::Mailbox>> taskQueue; - { - std::unique_lock<std::mutex> lock(m_taskQueueMutex); - std::swap(taskQueue, m_taskQueue); - } - - while (!taskQueue.empty()) { - mbgl::Mailbox::maybeReceive(taskQueue.front()); - taskQueue.pop(); - } + if (m_forceScheduler) { + getScheduler()->processEvents(); } } diff --git a/platform/qt/src/qmapboxgl_map_renderer.hpp b/platform/qt/src/qmapboxgl_map_renderer.hpp index adba11de51..0b17542e2f 100644 --- a/platform/qt/src/qmapboxgl_map_renderer.hpp +++ b/platform/qt/src/qmapboxgl_map_renderer.hpp @@ -14,7 +14,6 @@ #include <memory> #include <mutex> -#include <queue> namespace mbgl { class Renderer; @@ -23,7 +22,7 @@ class UpdateParameters; class QMapboxGLRendererBackend; -class QMapboxGLMapRenderer : public QObject, public mbgl::Scheduler +class QMapboxGLMapRenderer : public QObject { Q_OBJECT @@ -32,9 +31,6 @@ public: mbgl::ThreadPool &, QMapboxGLSettings::GLContextMode); virtual ~QMapboxGLMapRenderer(); - // mbgl::Scheduler implementation. - void schedule(std::weak_ptr<mbgl::Mailbox> scheduled) final; - void render(); void updateFramebuffer(quint32 fbo, const mbgl::Size &size); void setObserver(std::shared_ptr<mbgl::RendererObserver>); @@ -56,8 +52,5 @@ private: QMapboxGLRendererBackend m_backend; std::unique_ptr<mbgl::Renderer> m_renderer; - std::mutex m_taskQueueMutex; - std::queue<std::weak_ptr<mbgl::Mailbox>> m_taskQueue; - - bool m_threadWithScheduler; + bool m_forceScheduler; }; diff --git a/platform/qt/src/qmapboxgl_scheduler.cpp b/platform/qt/src/qmapboxgl_scheduler.cpp new file mode 100644 index 0000000000..e2d39703ee --- /dev/null +++ b/platform/qt/src/qmapboxgl_scheduler.cpp @@ -0,0 +1,38 @@ +#include "qmapboxgl_scheduler.hpp" + +#include <mbgl/util/util.hpp> + +#include <cassert> + +QMapboxGLScheduler::QMapboxGLScheduler() +{ +} + +QMapboxGLScheduler::~QMapboxGLScheduler() +{ + MBGL_VERIFY_THREAD(tid); +} + +void QMapboxGLScheduler::schedule(std::weak_ptr<mbgl::Mailbox> mailbox) +{ + std::lock_guard<std::mutex> lock(m_taskQueueMutex); + m_taskQueue.push(mailbox); + + // Need to force the main thread to wake + // up this thread and process the events. + emit needsProcessing(); +} + +void QMapboxGLScheduler::processEvents() +{ + std::queue<std::weak_ptr<mbgl::Mailbox>> taskQueue; + { + std::unique_lock<std::mutex> lock(m_taskQueueMutex); + std::swap(taskQueue, m_taskQueue); + } + + while (!taskQueue.empty()) { + mbgl::Mailbox::maybeReceive(taskQueue.front()); + taskQueue.pop(); + } +} diff --git a/platform/qt/src/qmapboxgl_scheduler.hpp b/platform/qt/src/qmapboxgl_scheduler.hpp new file mode 100644 index 0000000000..68636d0d11 --- /dev/null +++ b/platform/qt/src/qmapboxgl_scheduler.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include <mbgl/actor/mailbox.hpp> +#include <mbgl/actor/scheduler.hpp> +#include <mbgl/util/util.hpp> + +#include <QObject> + +#include <memory> +#include <mutex> +#include <queue> + +class QMapboxGLScheduler : public QObject, public mbgl::Scheduler +{ + Q_OBJECT + +public: + QMapboxGLScheduler(); + virtual ~QMapboxGLScheduler(); + + // mbgl::Scheduler implementation. + void schedule(std::weak_ptr<mbgl::Mailbox> scheduled) final; + + void processEvents(); + +signals: + void needsProcessing(); + +private: + MBGL_STORE_THREAD(tid); + + std::mutex m_taskQueueMutex; + std::queue<std::weak_ptr<mbgl::Mailbox>> m_taskQueue; +}; diff --git a/platform/qt/src/qt_geojson.cpp b/platform/qt/src/qt_geojson.cpp index 80377de64d..9d0a3e96eb 100644 --- a/platform/qt/src/qt_geojson.cpp +++ b/platform/qt/src/qt_geojson.cpp @@ -72,10 +72,8 @@ mbgl::Value asMapboxGLPropertyValue(const QVariant &value) { auto valueMap = [](const QVariantMap &map) { std::unordered_map<std::string, mbgl::Value> mbglMap; mbglMap.reserve(map.size()); - auto it = map.constBegin(); - while (it != map.constEnd()) { + for (auto it = map.constBegin(); it != map.constEnd(); ++it) { mbglMap.emplace(std::make_pair(it.key().toStdString(), asMapboxGLPropertyValue(it.value()))); - ++it; } return mbglMap; }; @@ -132,8 +130,7 @@ mbgl::FeatureIdentifier asMapboxGLFeatureIdentifier(const QVariant &id) { mbgl::Feature asMapboxGLFeature(const QMapbox::Feature &feature) { mbgl::PropertyMap properties; properties.reserve(feature.properties.size()); - auto it = feature.properties.constBegin(); - while (it != feature.properties.constEnd()) { + for (auto it = feature.properties.constBegin(); it != feature.properties.constEnd(); ++it) { properties.emplace(std::make_pair(it.key().toStdString(), asMapboxGLPropertyValue(it.value()))); } diff --git a/platform/qt/src/sqlite3.cpp b/platform/qt/src/sqlite3.cpp index 4bcaea0e31..2ca09fd3ad 100644 --- a/platform/qt/src/sqlite3.cpp +++ b/platform/qt/src/sqlite3.cpp @@ -52,15 +52,6 @@ void checkDatabaseError(const QSqlDatabase &db) { } } -void checkDatabaseOpenError(const QSqlDatabase &db) { - // Assume every error when opening the data as CANTOPEN. Qt - // always returns -1 for `nativeErrorCode()` on database errors. - QSqlError lastError = db.lastError(); - if (lastError.type() != QSqlError::NoError) { - throw Exception { ResultCode::CantOpen, "Error opening the database." }; - } -} - namespace { QString incrementCounter() { static QAtomicInt count = 0; @@ -70,32 +61,9 @@ namespace { class DatabaseImpl { public: - DatabaseImpl(const char* filename, int flags) - : connectionName(QString::number(uint64_t(QThread::currentThread())) + incrementCounter()) + DatabaseImpl(QString connectionName_) + : connectionName(std::move(connectionName_)) { - if (!QSqlDatabase::drivers().contains("QSQLITE")) { - throw Exception { ResultCode::CantOpen, "SQLite driver not found." }; - } - - assert(!QSqlDatabase::contains(connectionName)); - auto db = QSqlDatabase::addDatabase("QSQLITE", connectionName); - - QString connectOptions = db.connectOptions(); - if (flags & OpenFlag::ReadOnly) { - if (!connectOptions.isEmpty()) connectOptions.append(';'); - connectOptions.append("QSQLITE_OPEN_READONLY"); - } - if (flags & OpenFlag::SharedCache) { - if (!connectOptions.isEmpty()) connectOptions.append(';'); - connectOptions.append("QSQLITE_ENABLE_SHARED_CACHE"); - } - - db.setConnectOptions(connectOptions); - db.setDatabaseName(QString(filename)); - - if (!db.open()) { - checkDatabaseOpenError(db); - } } ~DatabaseImpl() { @@ -104,6 +72,9 @@ public: checkDatabaseError(db); } + void setBusyTimeout(std::chrono::milliseconds timeout); + void exec(const std::string& sql); + QString connectionName; }; @@ -127,12 +98,47 @@ public: template <typename T> using optional = std::experimental::optional<T>; +mapbox::util::variant<Database, Exception> Database::tryOpen(const std::string &filename, int flags) { + if (!QSqlDatabase::drivers().contains("QSQLITE")) { + return Exception { ResultCode::CantOpen, "SQLite driver not found." }; + } -Database::Database(const std::string& file, int flags) - : impl(std::make_unique<DatabaseImpl>(file.c_str(), flags)) { - assert(impl); + QString connectionName = QString::number(uint64_t(QThread::currentThread())) + incrementCounter(); + + assert(!QSqlDatabase::contains(connectionName)); + auto db = QSqlDatabase::addDatabase("QSQLITE", connectionName); + + QString connectOptions = db.connectOptions(); + if (flags & OpenFlag::ReadOnly) { + if (!connectOptions.isEmpty()) connectOptions.append(';'); + connectOptions.append("QSQLITE_OPEN_READONLY"); + } + + db.setConnectOptions(connectOptions); + db.setDatabaseName(QString(filename.c_str())); + + if (!db.open()) { + // Assume every error when opening the data as CANTOPEN. Qt + // always returns -1 for `nativeErrorCode()` on database errors. + return Exception { ResultCode::CantOpen, "Error opening the database." }; + } + + return Database(std::make_unique<DatabaseImpl>(connectionName)); } +Database Database::open(const std::string &filename, int flags) { + auto result = tryOpen(filename, flags); + if (result.is<Exception>()) { + throw result.get<Exception>(); + } else { + return std::move(result.get<Database>()); + } +} + +Database::Database(std::unique_ptr<DatabaseImpl> impl_) + : impl(std::move(impl_)) +{} + Database::Database(Database &&other) : impl(std::move(other.impl)) { assert(impl); @@ -149,12 +155,15 @@ Database::~Database() { void Database::setBusyTimeout(std::chrono::milliseconds timeout) { assert(impl); + impl->setBusyTimeout(timeout); +} +void DatabaseImpl::setBusyTimeout(std::chrono::milliseconds timeout) { // std::chrono::milliseconds.count() is a long and Qt will cast // internally to int, so we need to make sure the limits apply. std::string timeoutStr = mbgl::util::toString(timeout.count() & INT_MAX); - auto db = QSqlDatabase::database(impl->connectionName); + auto db = QSqlDatabase::database(connectionName); QString connectOptions = db.connectOptions(); if (connectOptions.isEmpty()) { if (!connectOptions.isEmpty()) connectOptions.append(';'); @@ -165,19 +174,25 @@ void Database::setBusyTimeout(std::chrono::milliseconds timeout) { } db.setConnectOptions(connectOptions); if (!db.open()) { - checkDatabaseOpenError(db); + // Assume every error when opening the data as CANTOPEN. Qt + // always returns -1 for `nativeErrorCode()` on database errors. + throw Exception { ResultCode::CantOpen, "Error opening the database." }; } } void Database::exec(const std::string &sql) { assert(impl); + impl->exec(sql); +} + +void DatabaseImpl::exec(const std::string& sql) { QStringList statements = QString::fromStdString(sql).split(';', QString::SkipEmptyParts); statements.removeAll("\n"); for (QString statement : statements) { if (!statement.endsWith(';')) { statement.append(';'); } - QSqlQuery query(QSqlDatabase::database(impl->connectionName)); + QSqlQuery query(QSqlDatabase::database(connectionName)); query.prepare(statement); if (!query.exec()) { @@ -424,16 +439,16 @@ uint64_t Query::changes() const { } Transaction::Transaction(Database& db_, Mode mode) - : db(db_) { + : dbImpl(*db_.impl) { switch (mode) { case Deferred: - db.exec("BEGIN DEFERRED TRANSACTION"); + dbImpl.exec("BEGIN DEFERRED TRANSACTION"); break; case Immediate: - db.exec("BEGIN IMMEDIATE TRANSACTION"); + dbImpl.exec("BEGIN IMMEDIATE TRANSACTION"); break; case Exclusive: - db.exec("BEGIN EXCLUSIVE TRANSACTION"); + dbImpl.exec("BEGIN EXCLUSIVE TRANSACTION"); break; } } @@ -450,12 +465,12 @@ Transaction::~Transaction() { void Transaction::commit() { needRollback = false; - db.exec("COMMIT TRANSACTION"); + dbImpl.exec("COMMIT TRANSACTION"); } void Transaction::rollback() { needRollback = false; - db.exec("ROLLBACK TRANSACTION"); + dbImpl.exec("ROLLBACK TRANSACTION"); } } // namespace sqlite |