summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.clang-format6
-rw-r--r--.gitignore8
-rw-r--r--.gitmodules4
m---------.mason0
-rw-r--r--.travis.yml2
-rw-r--r--Makefile383
-rw-r--r--README.md1
-rw-r--r--android/cpp/jni.cpp10
-rw-r--r--android/cpp/native_map_view.cpp5
-rw-r--r--android/mapboxgl-app.gyp52
-rwxr-xr-xandroid/scripts/run-build.sh1
-rw-r--r--bin/render.cpp19
-rw-r--r--bin/render.gyp67
-rwxr-xr-xconfigure70
-rw-r--r--gyp/asset-fs.gypi52
-rw-r--r--gyp/asset-zip.gypi62
-rw-r--r--gyp/cache-sqlite.gypi64
-rw-r--r--gyp/common.gypi50
-rw-r--r--gyp/core.gypi (renamed from gyp/mbgl-core.gypi)91
-rw-r--r--gyp/headless-cgl.gypi18
-rw-r--r--gyp/headless-glx.gypi18
-rw-r--r--gyp/http-curl.gypi55
-rw-r--r--gyp/http-nsurl.gypi43
-rw-r--r--gyp/install.gypi87
-rwxr-xr-xgyp/link.py47
-rw-r--r--gyp/load.gypi6
-rw-r--r--gyp/mbgl-headless.gypi35
-rw-r--r--gyp/mbgl-ios.gypi65
-rw-r--r--gyp/mbgl-platform.gypi20
-rw-r--r--gyp/none.gypi20
-rw-r--r--gyp/platform-android.gypi (renamed from gyp/mbgl-android.gypi)72
-rw-r--r--gyp/platform-ios.gypi (renamed from gyp/mbgl-osx.gypi)42
-rw-r--r--gyp/platform-linux.gypi (renamed from gyp/mbgl-linux.gypi)72
-rw-r--r--gyp/platform-osx.gypi58
-rw-r--r--gyp/standalone.gypi32
-rw-r--r--gyp/version.gypi2
-rw-r--r--include/mbgl/android/jni.hpp6
-rw-r--r--include/mbgl/android/native_map_view.hpp8
-rw-r--r--include/mbgl/map/map.hpp10
-rw-r--r--include/mbgl/platform/default/headless_view.hpp1
-rw-r--r--include/mbgl/platform/platform.hpp7
-rw-r--r--include/mbgl/storage/asset_request.hpp27
-rw-r--r--include/mbgl/storage/asset_request_baton.hpp38
-rw-r--r--include/mbgl/storage/caching_http_file_source.hpp62
-rw-r--r--include/mbgl/storage/default/asset_request.hpp24
-rw-r--r--include/mbgl/storage/default/http_context.hpp76
-rw-r--r--include/mbgl/storage/default/http_request.hpp26
-rw-r--r--include/mbgl/storage/default/request.hpp51
-rw-r--r--include/mbgl/storage/default/shared_request_base.hpp88
-rw-r--r--include/mbgl/storage/default/sqlite_cache.hpp52
-rw-r--r--include/mbgl/storage/default/thread_context.hpp78
-rw-r--r--include/mbgl/storage/default_file_source.hpp60
-rw-r--r--include/mbgl/storage/file_cache.hpp27
-rw-r--r--include/mbgl/storage/file_source.hpp31
-rw-r--r--include/mbgl/storage/http_request_baton.hpp74
-rw-r--r--include/mbgl/storage/network_status.hpp25
-rw-r--r--include/mbgl/storage/request.hpp41
-rw-r--r--include/mbgl/storage/request_callback.hpp22
-rw-r--r--include/mbgl/storage/resource.hpp35
-rw-r--r--include/mbgl/storage/resource_type.hpp18
-rw-r--r--include/mbgl/storage/response.hpp14
-rw-r--r--include/mbgl/util/async_queue.hpp95
-rw-r--r--include/mbgl/util/util.hpp17
-rw-r--r--include/mbgl/util/uv.hpp12
-rw-r--r--include/mbgl/util/variant.hpp72
m---------ios/mapbox-gl-cocoa0
-rw-r--r--linux/main.cpp12
-rw-r--r--linux/mapboxgl-app.gyp53
-rw-r--r--macosx/main.mm38
-rw-r--r--macosx/mapboxgl-app.gyp75
-rw-r--r--mapboxgl.gyp13
-rw-r--r--mbgl.gyp26
-rw-r--r--platform/android/asset_request_libzip.cpp202
-rw-r--r--platform/android/cache_database_data.cpp13
-rw-r--r--platform/darwin/application_root.mm4
-rw-r--r--platform/darwin/asset_root.mm18
-rw-r--r--platform/darwin/http_request_baton_cocoa.mm155
-rw-r--r--platform/darwin/http_request_nsurl.mm419
-rw-r--r--platform/darwin/log_nslog.mm30
-rw-r--r--platform/darwin/string_nsstring.mm24
-rw-r--r--platform/default/application_root.cpp21
-rw-r--r--platform/default/asset_request_fs.cpp252
-rw-r--r--platform/default/asset_request_libuv.cpp223
-rw-r--r--platform/default/asset_request_zip.cpp301
-rw-r--r--platform/default/asset_root.cpp28
-rw-r--r--platform/default/cache_database_tmp.cpp12
-rw-r--r--platform/default/compression.cpp (renamed from src/mbgl/util/compression.cpp)17
-rw-r--r--platform/default/compression.hpp (renamed from src/mbgl/util/compression.hpp)0
-rw-r--r--platform/default/http_request_baton_curl.cpp629
-rw-r--r--platform/default/http_request_curl.cpp648
-rw-r--r--platform/default/sqlite3.cpp (renamed from src/mbgl/util/sqlite3.cpp)23
-rw-r--r--platform/default/sqlite3.hpp (renamed from src/mbgl/util/sqlite3.hpp)0
-rw-r--r--platform/default/sqlite_cache.cpp271
-rw-r--r--platform/default/uv_zip.c202
-rw-r--r--platform/default/uv_zip.h45
-rw-r--r--platform/ios/cache_database_library.mm21
-rw-r--r--platform/osx/cache_database_application_support.mm31
-rwxr-xr-xscripts/android_env.sh25
-rwxr-xr-xscripts/install_node.sh4
-rwxr-xr-xscripts/run_tests.sh54
-rwxr-xr-xscripts/travis_script.sh16
-rw-r--r--src/mbgl/geometry/buffer.hpp4
-rw-r--r--src/mbgl/geometry/elements_buffer.hpp5
-rw-r--r--src/mbgl/geometry/vao.cpp3
-rw-r--r--src/mbgl/geometry/vao.hpp15
-rw-r--r--src/mbgl/map/map.cpp81
-rw-r--r--src/mbgl/map/raster_tile_data.cpp4
-rw-r--r--src/mbgl/map/raster_tile_data.hpp2
-rw-r--r--src/mbgl/map/source.cpp18
-rw-r--r--src/mbgl/map/source.hpp4
-rw-r--r--src/mbgl/map/sprite.cpp14
-rw-r--r--src/mbgl/map/tile_data.cpp31
-rw-r--r--src/mbgl/map/tile_data.hpp9
-rw-r--r--src/mbgl/map/vector_tile_data.cpp18
-rw-r--r--src/mbgl/map/vector_tile_data.hpp2
-rw-r--r--src/mbgl/renderer/fill_bucket.cpp48
-rw-r--r--src/mbgl/renderer/fill_bucket.hpp4
-rw-r--r--src/mbgl/renderer/line_bucket.cpp72
-rw-r--r--src/mbgl/renderer/line_bucket.hpp4
-rw-r--r--src/mbgl/renderer/symbol_bucket.cpp46
-rw-r--r--src/mbgl/renderer/symbol_bucket.hpp10
-rw-r--r--src/mbgl/storage/asset_request.hpp27
-rw-r--r--src/mbgl/storage/base_request.cpp87
-rw-r--r--src/mbgl/storage/base_request.hpp62
-rw-r--r--src/mbgl/storage/caching_http_file_source.cpp136
-rw-r--r--src/mbgl/storage/default_file_source.cpp245
-rw-r--r--src/mbgl/storage/http_request.cpp280
-rw-r--r--src/mbgl/storage/http_request.hpp58
-rw-r--r--src/mbgl/storage/http_request_baton.cpp12
-rw-r--r--src/mbgl/storage/network_status.cpp32
-rw-r--r--src/mbgl/storage/request.cpp104
-rw-r--r--src/mbgl/storage/response.cpp22
-rw-r--r--src/mbgl/storage/sqlite_store.cpp228
-rw-r--r--src/mbgl/storage/sqlite_store.hpp49
-rw-r--r--src/mbgl/style/style.hpp3
-rw-r--r--src/mbgl/style/style_bucket.cpp12
-rw-r--r--src/mbgl/style/style_bucket.hpp31
-rw-r--r--src/mbgl/text/glyph_store.cpp33
-rw-r--r--src/mbgl/util/merge_lines.cpp99
-rw-r--r--src/mbgl/util/merge_lines.hpp100
-rw-r--r--src/mbgl/util/uv-worker.c8
-rw-r--r--src/mbgl/util/uv-worker.h2
-rw-r--r--src/mbgl/util/uv.cpp8
-rw-r--r--test/fixtures/fixture_log.cpp63
-rw-r--r--test/fixtures/fixture_log.hpp53
-rw-r--r--test/fixtures/main.cpp (renamed from test/main.cpp)2
-rw-r--r--test/fixtures/storage/empty0
-rw-r--r--test/fixtures/util.cpp35
-rw-r--r--test/fixtures/util.hpp23
-rw-r--r--test/headless/headless.cpp (renamed from test/headless.cpp)136
-rwxr-xr-xtest/headless/server.js19
-rw-r--r--test/miscellaneous/clip_ids.cpp (renamed from test/clip_ids.cpp)2
-rw-r--r--test/miscellaneous/comparisons.cpp (renamed from test/comparisons.cpp)2
-rw-r--r--test/miscellaneous/enums.cpp (renamed from test/enums.cpp)2
-rw-r--r--test/miscellaneous/functions.cpp (renamed from test/functions.cpp)2
-rw-r--r--test/miscellaneous/merge_lines.cpp (renamed from test/merge_lines.cpp)20
-rw-r--r--test/miscellaneous/rotation_range.cpp (renamed from test/rotation_range.cpp)2
-rw-r--r--test/miscellaneous/style_parser.cpp (renamed from test/style_parser.cpp)33
-rw-r--r--test/miscellaneous/text_conversions.cpp (renamed from test/text_conversions.cpp)2
-rw-r--r--test/miscellaneous/tile.cpp (renamed from test/tile.cpp)2
-rw-r--r--test/miscellaneous/variant.cpp (renamed from test/variant.cpp)2
-rw-r--r--test/storage/cache_response.cpp39
-rw-r--r--test/storage/cache_revalidate.cpp85
-rw-r--r--test/storage/directory_reading.cpp25
-rw-r--r--test/storage/file_reading.cpp50
-rw-r--r--test/storage/http_cancel.cpp50
-rw-r--r--test/storage/http_coalescing.cpp48
-rw-r--r--test/storage/http_error.cpp63
-rw-r--r--test/storage/http_header_parsing.cpp42
-rw-r--r--test/storage/http_load.cpp42
-rw-r--r--test/storage/http_noloop.cpp37
-rw-r--r--test/storage/http_other_loop.cpp26
-rw-r--r--test/storage/http_reading.cpp39
-rwxr-xr-xtest/storage/server.js101
-rw-r--r--test/storage/storage.cpp14
-rw-r--r--test/storage/storage.hpp16
-rw-r--r--test/test.gyp318
-rwxr-xr-xutils/mbgl-config/build.sh7
-rw-r--r--utils/mbgl-config/mbgl-config.template.sh8
179 files changed, 6001 insertions, 4082 deletions
diff --git a/.clang-format b/.clang-format
index 0565d7379e..86d562e74e 100644
--- a/.clang-format
+++ b/.clang-format
@@ -2,9 +2,11 @@ Standard: Cpp11
IndentWidth: 4
AccessModifierOffset: -4
UseTab: Never
-BinPackParameters: true
+BinPackParameters: false
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
+AllowShortBlocksOnASingleLine: false
+AllowShortFunctionsOnASingleLine: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
AlwaysBreakTemplateDeclarations: true
NamespaceIndentation: None
@@ -12,5 +14,5 @@ PointerBindsToType: false
SpacesInParentheses: false
BreakBeforeBraces: Attach
ColumnLimit: 100
-Cpp11BracedListStyle: true
+Cpp11BracedListStyle: false
SpacesBeforeTrailingComments: 1
diff --git a/.gitignore b/.gitignore
index 98d9e28281..f0a0360782 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,11 +3,13 @@
*.o
*.actual.png
*.diff.png
+*.pyc
/mason_packages
-/config.gypi
-/config-ios.gypi
-/config-android.gypi
+/config-*.gypi
/build
+/macosx/build
+/test/build
+/test/node_modules
/include/mbgl/shader/shaders.hpp
/src/shader/shaders_gl.cpp
/src/shader/shaders_gles2.cpp
diff --git a/.gitmodules b/.gitmodules
index 588802377e..8242fab9b2 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -13,3 +13,7 @@
[submodule "styles"]
path = styles
url = https://github.com/mapbox/mapbox-gl-styles.git
+
+[submodule "app/ios"]
+ path = app/ios
+ url = https://github.com/mapbox/mapbox-gl-cocoa.git \ No newline at end of file
diff --git a/.mason b/.mason
-Subproject 3d9c0c829f19b3f133ea2fdbe0be8b2ba9ef465
+Subproject 03869fd11e4bc619d3ba7e4f8a1f00d4612c90e
diff --git a/.travis.yml b/.travis.yml
index 9dccffe9e6..e42f9fde7b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -53,13 +53,13 @@ before_install:
- if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then sudo service postgresql stop; fi
- source ./scripts/local_mason.sh
- source ./scripts/travis_helper.sh
+- source ./scripts/install_node.sh
- source ./scripts/flags.sh
- ./scripts/travis_before_install.sh
- if [[ ${TRAVIS_OS_NAME} == "linux" && ${MASON_PLATFORM} != "android" ]]; then export LD_LIBRARY_PATH=`mason prefix mesa 10.4.3`/lib; fi
- if [[ ${TRAVIS_OS_NAME} == "linux" && ${MASON_PLATFORM} != "android" ]]; then glxinfo; fi
install:
-- if [[ ${MASON_PLATFORM} != "android" ]]; then make config.gypi; fi
- ulimit -c
before_script:
diff --git a/Makefile b/Makefile
index 7d2fd5e6f4..c8660f1625 100644
--- a/Makefile
+++ b/Makefile
@@ -1,143 +1,261 @@
BUILDTYPE ?= Release
PYTHON ?= python
-V ?= 1
PREFIX ?= /usr/local
+ANDROID_ABI ?= arm-v7
JOBS ?= 1
ifeq ($(shell uname -s), Darwin)
-PLATFORM ?= osx
+HOST ?= osx
endif
-PLATFORM ?= linux
+HOST ?= linux
+
+all: mbgl
+
+#### Configuration defaults ####################################################
+
+ENV_osx = MASON_PLATFORM=osx
+CONFIG_osx = -Dhost=osx -Iconfig-osx.gypi -Dinstall_prefix=$(PREFIX)
+LIBS_osx = -Dheadless_lib=$(word 1,$(HEADLESS) cgl)
+LIBS_osx += -Dplatform_lib=$(word 1,$(PLATFORM) osx)
+LIBS_osx += -Dasset_lib=$(word 1,$(ASSET) fs)
+LIBS_osx += -Dhttp_lib=$(word 1,$(HTTP) nsurl)
+LIBS_osx += -Dcache_lib=$(word 1,$(CACHE) sqlite)
+LIBS_osx += --depth=. -Goutput_dir=.
+
+
+ENV_ios = MASON_PLATFORM=ios
+CONFIG_ios = -Dhost=ios -Iconfig-ios.gypi -Dinstall_prefix=$(PREFIX)
+LIBS_ios = -Dheadless_lib=none
+LIBS_ios += -Dplatform_lib=$(word 1,$(PLATFORM) ios)
+LIBS_ios += -Dasset_lib=$(word 1,$(ASSET) fs)
+LIBS_ios += -Dhttp_lib=$(word 1,$(HTTP) nsurl)
+LIBS_ios += -Dcache_lib=$(word 1,$(CACHE) sqlite)
+LIBS_ios += --depth=. -Goutput_dir=.
+
+
+ENV_linux = MASON_PLATFORM=linux
+CONFIG_linux = -Dhost=linux -Iconfig-linux.gypi -Dinstall_prefix=$(PREFIX)
+LIBS_linux = -Dheadless_lib=$(word 1,$(HEADLESS) glx)
+LIBS_linux += -Dplatform_lib=$(word 1,$(PLATFORM) linux)
+LIBS_linux += -Dasset_lib=$(word 1,$(ASSET) fs)
+LIBS_linux += -Dhttp_lib=$(word 1,$(HTTP) curl)
+LIBS_linux += -Dcache_lib=$(word 1,$(CACHE) sqlite)
+LIBS_linux += --depth=. -Goutput_dir=.
+
+ANDROID_ABIS += android-lib-arm-v8
+ENV_android-arm-v8 = $(shell MASON_ANDROID_ABI=arm-v8 ./scripts/android_env.sh)
+CONFIG_android-arm-v8 = -Dhost=android -Iconfig-android-arm-v8.gypi
+
+ANDROID_ABIS += android-lib-arm-v7
+ENV_android-arm-v7 = $(shell MASON_ANDROID_ABI=arm-v7 ./scripts/android_env.sh)
+CONFIG_android-arm-v7 = -Dhost=android -Iconfig-android-arm-v7.gypi
+
+ANDROID_ABIS += android-lib-arm-v5
+ENV_android-arm-v5 = $(shell MASON_ANDROID_ABI=arm-v5 ./scripts/android_env.sh)
+CONFIG_android-arm-v5 = -Dhost=android -Iconfig-android-arm-v5.gypi
+
+ANDROID_ABIS += android-lib-x86
+ENV_android-x86 = $(shell MASON_ANDROID_ABI=x86 ./scripts/android_env.sh)
+CONFIG_android-x86 = -Dhost=android -Iconfig-android-x86.gypi
+
+# OpenSSL build is incomplete.
+# ANDROID_ABIS += android-lib-x86-64
+# ENV_android-x86-64 = $(shell MASON_ANDROID_ABI=x86-64 ./scripts/android_env.sh)
+# CONFIG_android-x86-64 = -Dhost=android -Iconfig-android-x86-64.gypi
+
+ANDROID_ABIS += android-lib-mips
+ENV_android-mips = $(shell MASON_ANDROID_ABI=mips ./scripts/android_env.sh)
+CONFIG_android-mips = -Dhost=android -Iconfig-android-mips.gypi
+
+ANDROID_ABIS += android-lib-mips-64
+ENV_android-mips-64 = $(shell MASON_ANDROID_ABI=mips-64 ./scripts/android_env.sh)
+CONFIG_android-mips-64 = -Dhost=android -Iconfig-android-mips-64.gypi
+
+LIBS_android = -Dheadless_lib=none
+LIBS_android += -Dplatform_lib=$(word 1,$(PLATFORM) android)
+LIBS_android += -Dasset_lib=$(word 1,$(ASSET) zip)
+LIBS_android += -Dhttp_lib=$(word 1,$(HTTP) curl)
+LIBS_android += -Dcache_lib=$(word 1,$(CACHE) sqlite)
+LIBS_android += --depth=. -Goutput_dir=.
+
+#### Dependencies ##############################################################
+
+# Wildcard targets get removed after build by default, but we want to preserve the config.
+.PRECIOUS: config-%.gypi
+config-%.gypi: CMD = ./configure config-$*.gypi
+config-%.gypi: configure
+ @echo $(CMD)
+ @$(ENV_$*) $(CMD)
+ cat config-$*.gypi
-.PHONY: all
-all: mbgl-core mbgl-platform mbgl-headless
+#### Library builds ############################################################
-config.gypi: configure
- ./configure
+.PRECOIUS: Makefile/mbgl
+Makefile/mbgl: config-$(HOST).gypi
+ deps/run_gyp mbgl.gyp $(CONFIG_$(HOST)) $(LIBS_$(HOST)) --generator-output=./build/$(HOST) -f make
-config-ios.gypi: configure
- MASON_PLATFORM=ios ./configure config-ios.gypi
+mbgl: Makefile/mbgl
+ $(MAKE) -C build/$(HOST) BUILDTYPE=$(BUILDTYPE) everything
-#### Library builds ############################################################
+standalone: Makefile/mbgl
+ LINK=`pwd`/gyp/link.py $(MAKE) -C build/$(HOST) BUILDTYPE=$(BUILDTYPE) standalone
-.PHONY: mbgl-core
-mbgl-core: build/mbgl/Makefile
- $(MAKE) -C build/mbgl BUILDTYPE=$(BUILDTYPE) V=$(V) mbgl-core
-
-.PHONY: mbgl-platform
-mbgl-platform: build/mbgl/Makefile
- $(MAKE) -C build/mbgl BUILDTYPE=$(BUILDTYPE) V=$(V) mbgl-$(PLATFORM)
-
-.PHONY: mbgl-headless
-mbgl-headless: build/mbgl/Makefile
- $(MAKE) -C build/mbgl BUILDTYPE=$(BUILDTYPE) V=$(V) mbgl-headless
-
-.PHONY: install
-install: build/mbgl/Makefile
- $(MAKE) -C build/mbgl BUILDTYPE=$(BUILDTYPE) V=$(V) install
-
-#### Build scripts #############################################################
-
-.PHONY: build/mbgl/Makefile
-build/mbgl/Makefile: mapboxgl.gyp config.gypi
- deps/run_gyp mapboxgl.gyp -Iconfig.gypi -Dplatform=$(PLATFORM) -Dinstall_prefix=$(PREFIX) --depth=. -Goutput_dir=.. --generator-output=./build/mbgl -f make
-
-.PHONY: build/test/Makefile
-build/test/Makefile: test/test.gyp config.gypi
- deps/run_gyp test/test.gyp -Iconfig.gypi -Dplatform=$(PLATFORM) --depth=. -Goutput_dir=.. --generator-output=./build/test -f make
-
-.PHONY: build/linux/Makefile
-build/linux/Makefile: linux/mapboxgl-app.gyp config.gypi
- deps/run_gyp linux/mapboxgl-app.gyp -Iconfig.gypi -Dplatform=linux --depth=. -Goutput_dir=.. --generator-output=./build/linux -f make
-
-.PHONY: build/macosx/Makefile
-build/macosx/Makefile: macosx/mapboxgl-app.gyp config.gypi
- deps/run_gyp macosx/mapboxgl-app.gyp -Iconfig.gypi -Dplatform=osx --depth=. -Goutput_dir=.. --generator-output=./build/macosx -f make
-
-.PHONY: build/render/Makefile
-build/render/Makefile: bin/render.gyp config.gypi
- deps/run_gyp bin/render.gyp -Iconfig.gypi -Dplatform=$(PLATFORM) --depth=. -Goutput_dir=.. --generator-output=./build/render -f make
-
-.PHONY: build/test/test.xcodeproj
-build/test/test.xcodeproj: test/test.gyp config.gypi
- deps/run_gyp test/test.gyp -Iconfig.gypi -Dplatform=$(PLATFORM) --depth=. -Goutput_dir=.. --generator-output=./build -f xcode
-
-.PHONY: build/macosx/mapboxgl-app.xcodeproj
-build/macosx/mapboxgl-app.xcodeproj: macosx/mapboxgl-app.gyp config.gypi
- deps/run_gyp macosx/mapboxgl-app.gyp -Iconfig.gypi -Dplatform=osx --depth=. --generator-output=./build -f xcode
-
-.PHONY: build/ios/mapbox-gl-cocoa/app/mapboxgl-app.xcodeproj
-build/ios/mapbox-gl-cocoa/app/mapboxgl-app.xcodeproj: ios/mapbox-gl-cocoa/app/mapboxgl-app.gyp config-ios.gypi
- deps/run_gyp ios/mapbox-gl-cocoa/app/mapboxgl-app.gyp -Iconfig-ios.gypi -Dplatform=ios --depth=. --generator-output=./build -f xcode
-
-.PHONY: build/linux/mapboxgl-app.xcodeproj
-build/linux/mapboxgl-app.xcodeproj: linux/mapboxgl-app.gyp config.gypi
- deps/run_gyp linux/mapboxgl-app.gyp -Iconfig.gypi -Dplatform=linux --depth=. --generator-output=./build -f xcode
-
-.PHONY: build/bin/render.xcodeproj
- build/bin/render.xcodeproj: bin/render.gyp config.gypi
- deps/run_gyp bin/render.gyp -Iconfig.gypi -Dplatform=$(PLATFORM) --depth=. --generator-output=./build -f xcode
-
-.PHONY: android
-android:
- ./scripts/local_mason.sh && \
- MASON_DIR=./.mason MASON_PLATFORM=android MASON_ANDROID_ABI=$(MASON_ANDROID_ABI) ./.mason/mason env PATH && \
- export CXX="`MASON_DIR=./.mason MASON_PLATFORM=android MASON_ANDROID_ABI=${MASON_ANDROID_ABI} ./.mason/mason env CXX`" && \
- export CC="`MASON_DIR=./.mason MASON_PLATFORM=android MASON_ANDROID_ABI=${MASON_ANDROID_ABI} ./.mason/mason env CC`" && \
- export LD="`MASON_DIR=./.mason MASON_PLATFORM=android MASON_ANDROID_ABI=${MASON_ANDROID_ABI} ./.mason/mason env LD`" && \
- export LINK="`MASON_DIR=./.mason MASON_PLATFORM=android MASON_ANDROID_ABI=${MASON_ANDROID_ABI} ./.mason/mason env CXX`" && \
- export AR="`MASON_DIR=./.mason MASON_PLATFORM=android MASON_ANDROID_ABI=${MASON_ANDROID_ABI} ./.mason/mason env AR`" && \
- export RANLIB="`MASON_DIR=./.mason MASON_PLATFORM=android MASON_ANDROID_ABI=${MASON_ANDROID_ABI} ./.mason/mason env RANLIB`" && \
- export STRIP="`MASON_DIR=./.mason MASON_PLATFORM=android MASON_ANDROID_ABI=${MASON_ANDROID_ABI} ./.mason/mason env STRIP`" && \
- export LDFLAGS="`MASON_DIR=./.mason MASON_PLATFORM=android MASON_ANDROID_ABI=${MASON_ANDROID_ABI} ./.mason/mason env LDFLAGS` ${LDFLAGS}" && \
- export CFLAGS="`MASON_DIR=./.mason MASON_PLATFORM= MASON_ANDROID_ABI=${MASON_ANDROID_ABI} ./.mason/mason env CFLAGS` ${CFLAGS}" && \
- export CPPFLAGS="`MASON_DIR=./.mason MASON_PLATFORM=android MASON_ANDROID_ABI=${MASON_ANDROID_ABI} ./.mason/mason env CPPFLAGS` ${CPPFLAGS}" && \
- export PATH="`MASON_DIR=./.mason MASON_PLATFORM=android MASON_ANDROID_ABI=${MASON_ANDROID_ABI} ./.mason/mason env PATH`:${PATH}" && \
- export JNIDIR="`MASON_DIR=./.mason MASON_PLATFORM=android MASON_ANDROID_ABI=${MASON_ANDROID_ABI} ./.mason/mason env JNIDIR`" && \
- MASON_PLATFORM=android MASON_ANDROID_ABI=$(MASON_ANDROID_ABI) ./configure config-android.gypi && \
- deps/run_gyp android/mapboxgl-app.gyp -Iconfig-android.gypi -Dplatform=android --depth=. --generator-output=./build/android/$(MASON_ANDROID_ABI) -f make-android && \
- $(MAKE) -C build/android/$(MASON_ANDROID_ABI) -j$(JOBS) BUILDTYPE=$(BUILDTYPE) V=$(V) androidapp && \
- BUILDTYPE=$(BUILDTYPE) MASON_ANDROID_ABI=$(MASON_ANDROID_ABI) ./android/scripts/copy-files.sh && \
- cd android/java && \
- ./gradlew --parallel-threads=$(JOBS) build
-
-##### Test cases ###############################################################
-
-test: build/test/Makefile
- $(MAKE) -C build/test BUILDTYPE=$(BUILDTYPE) V=$(V) test
-
-test_%: build/test/Makefile
- $(MAKE) -C build/test BUILDTYPE=$(BUILDTYPE) V=$(V) $*
- (cd build/$(BUILDTYPE) && exec ./test_$*)
-
-# build Mac OS X project for Xcode
-xtest: build/test/test.xcodeproj
- open ./build/test/test.xcodeproj
-
-##### Makefile builds ##########################################################
-
-
-# Builds the linux app with make.
-linux: build/linux/Makefile
- $(MAKE) -C build/linux BUILDTYPE=$(BUILDTYPE) V=$(V) linuxapp
-
-# Executes the Linux binary
-run-linux: linux
- (cd build/$(BUILDTYPE) && ./mapbox-gl)
+install: Makefile/mbgl
+ LINK=`pwd`/gyp/link.py $(MAKE) -C build/$(HOST) BUILDTYPE=$(BUILDTYPE) install
+
+
+##### Test builds ##############################################################
+
+.PRECIOUS: Makefile/test
+Makefile/test: test/test.gyp config-$(HOST).gypi
+ deps/run_gyp test/test.gyp $(CONFIG_$(HOST)) $(LIBS_$(HOST)) --generator-output=./build/$(HOST) -f make
+
+test: Makefile/test
+ $(MAKE) -C build/$(HOST) BUILDTYPE=$(BUILDTYPE) test
+
+test-%: test
+ ./scripts/run_tests.sh "build/$(HOST)/$(BUILDTYPE)/test" --gtest_filter=$*
+
+
+.PRECIOUS: Xcode/test
+Xcode/test: test/test.gyp config-osx.gypi
+ deps/run_gyp test/test.gyp $(CONFIG_osx) $(LIBS_osx) --generator-output=./build/osx -f xcode
+
+.PHONY: lproj lbuild run-xlinux
+xtest-proj: Xcode/test
+ open ./build/osx/test/test.xcodeproj
+
+xtest: Xcode/test
+ xcodebuild -project ./build/osx/test/test.xcodeproj -configuration $(BUILDTYPE) -target test -jobs `sysctl -n hw.ncpu`
+
+xtest-%: xtest
+ ./scripts/run_tests.sh "build/osx/Build/Products/$(BUILDTYPE)/test" --gtest_filter=$*
+
+
+#### Mac OS X application builds ###############################################
-# Builds the OS X app with make.
-osx: build/macosx/Makefile
- $(MAKE) -C build/macosx BUILDTYPE=$(BUILDTYPE) V=$(V) osxapp
+.PRECIOUS: Makefile/osx
+Makefile/osx: macosx/mapboxgl-app.gyp config-osx.gypi
+ deps/run_gyp macosx/mapboxgl-app.gyp $(CONFIG_osx) $(LIBS_osx) --generator-output=./build/osx -f make
+
+.PHONY: osx run-osx
+osx: Makefile/osx
+ $(MAKE) -C build/osx BUILDTYPE=$(BUILDTYPE) osxapp
-# Executes the OS X binary
run-osx: osx
- build/$(BUILDTYPE)/Mapbox\ GL.app/Contents/MacOS/MAPBOX\ GL
+ "build/osx/$(BUILDTYPE)/Mapbox GL.app/Contents/MacOS/Mapbox GL"
+
+
+.PRECIOUS: Xcode/osx
+Xcode/osx: macosx/mapboxgl-app.gyp config-osx.gypi
+ deps/run_gyp macosx/mapboxgl-app.gyp $(CONFIG_osx) $(LIBS_osx) --generator-output=./build/osx -f xcode
+
+.PHONY: xosx-proj xosx run-xosx
+xosx-proj: Xcode/osx
+ open ./build/osx/macosx/mapboxgl-app.xcodeproj
+
+xosx: Xcode/osx
+ xcodebuild -project ./build/osx/macosx/mapboxgl-app.xcodeproj -configuration $(BUILDTYPE) -target osxapp -jobs `sysctl -n hw.ncpu`
+
+run-xosx: xosx
+ "build/osx/Build/Products/$(BUILDTYPE)/Mapbox GL.app/Contents/MacOS/Mapbox GL"
+
+# Legacy name
+xproj: xosx-proj
+
+
+
+#### iOS application builds ####################################################
+
+.PRECIOUS: Xcode/ios
+Xcode/ios: ios/mapbox-gl-cocoa/app/mapboxgl-app.gyp config-ios.gypi
+ deps/run_gyp ios/mapbox-gl-cocoa/app/mapboxgl-app.gyp $(CONFIG_ios) $(LIBS_ios) --generator-output=./build/ios -f xcode
+
+.PHONY: ios-proj ios run-ios
+ios-proj: Xcode/ios
+ open ./build/ios/ios/mapbox-gl-cocoa/app/mapboxgl-app.xcodeproj
+
+ios: Xcode/ios
+ xcodebuild -sdk iphonesimulator ARCHS=x86_64 -project ./build/ios/ios/mapbox-gl-cocoa/app/mapboxgl-app.xcodeproj -configuration $(BUILDTYPE) -target iosapp -jobs `sysctl -n hw.ncpu`
+
+# Legacy name
+iproj: ios-proj
+
-# Builds the CLI render app
-render: build/render/Makefile
- $(MAKE) -C build/render BUILDTYPE=$(BUILDTYPE) V=$(V) mbgl-render
+#### Linux application builds ##################################################
-##### Xcode projects ###########################################################
+.PRECIOUS: Makefile/linux
+Makefile/linux: linux/mapboxgl-app.gyp config-$(HOST).gypi
+ deps/run_gyp linux/mapboxgl-app.gyp $(CONFIG_$(HOST)) $(LIBS_linux) --generator-output=./build/$(HOST) -f make
+
+.PHONY: linux run-linux
+linux: Makefile/linux
+ $(MAKE) -C build/$(HOST) BUILDTYPE=$(BUILDTYPE) linuxapp
+
+run-linux: linux
+ (cd build/$(HOST)/$(BUILDTYPE) && ./mapbox-gl)
+
+
+.PRECIOUS: Xcode/linux
+Xcode/linux: linux/mapboxgl-app.gyp config-osx.gypi
+ deps/run_gyp linux/mapboxgl-app.gyp $(CONFIG_osx) $(LIBS_linux) --generator-output=./build/osx -f xcode
+
+.PHONY: lproj lbuild run-xlinux
+xlinux-proj: Xcode/linux
+ open ./build/osx/linux/mapboxgl-app.xcodeproj
+
+xlinux: Xcode/linux
+ xcodebuild -project ./build/osx/linux/mapboxgl-app.xcodeproj -configuration $(BUILDTYPE) -target linuxapp
+
+run-xlinux: xlinux
+ "build/osx/Build/Products/$(BUILDTYPE)/mapbox-gl"
+
+# Legacy name
+lproj: xlinux-proj
+
+
+##### Render application #######################################################
+# .PHONY: build/render/Makefile
+# build/render/Makefile: bin/render.gyp __$(HOST)__/render
+# __%__/render: config-%.gypi
+# deps/run_gyp bin/render.gyp -Iconfig-$*.gypi $(CONFIG_STRING) --generator-output=./build/render -f make
+
+# .PHONY: build/bin/render.xcodeproj
+# build/bin/render.xcodeproj: bin/render.gyp __osx__/render-xcode
+# __osx__/render-xcode:config-osx.gypi
+# deps/run_gyp bin/render.gyp -Iconfig-osx.gypi $(CONFIG_STRING) --generator-output=./build -f xcode
+
+# # Builds the CLI render app
+# render: build/render/Makefile
+# $(MAKE) -C build/render BUILDTYPE=$(BUILDTYPE) mbgl-render
+
+#### Android libaries #########################################################
+
+.PRECIOUS: Makefile/android-%
+Makefile/android-%: CMD = deps/run_gyp android/mapboxgl-app.gyp $(CONFIG_android-$*) $(LIBS_android) --generator-output=./build/android-$* -f make-android
+Makefile/android-%: config-android-%.gypi
+ @echo $(CMD)
+ @$(ENV_android-$*) $(CMD)
+
+# Builds all android architectures.
+android-all: $(ANDROID_ABIS)
+android-all: android
+
+# Builds a particular android architecture.
+android-lib-%: CMD = $(MAKE) -C build/android-$* BUILDTYPE=$(BUILDTYPE) androidapp
+android-lib-%: Makefile/android-%
+ @echo $(CMD)
+ @$(ENV_android-$*) $(CMD)
+
+# Builds the selected/default Android library
+android: android-lib-$(ANDROID_ABI)
+ cd android/java && ./gradlew --parallel-threads=$(JOBS) build
+
+# rproj: build/bin/render.xcodeproj
+# open ./build/bin/render.xcodeproj
+
+
+##### Maintenace operations ####################################################
.PHONY: clear_xcode_cache
clear_xcode_cache:
@@ -153,22 +271,6 @@ ifeq ($(PLATFORM), osx)
fi
endif
-xproj: build/macosx/mapboxgl-app.xcodeproj
- open ./build/macosx/mapboxgl-app.xcodeproj
-
-iproj: build/ios/mapbox-gl-cocoa/app/mapboxgl-app.xcodeproj
- open ./build/ios/mapbox-gl-cocoa/app/mapboxgl-app.xcodeproj
-
-rproj: build/bin/render.xcodeproj
- open ./build/bin/render.xcodeproj
-
-# build Linux project for Xcode (Runs on Mac OS X too, but without platform-specific code)
-lproj: build/linux/mapboxgl-app.xcodeproj
- open ./build/linux/mapboxgl-app.xcodeproj
-
-
-##### Maintenace operations ####################################################
-
.PHONY: clear_sqlite_cache
clear_sqlite_cache:
ifeq ($(PLATFORM), osx)
@@ -180,8 +282,7 @@ endif
clean: clear_sqlite_cache clear_xcode_cache
-find ./deps/gyp -name "*.pyc" -exec rm {} \;
-rm -rf ./build/
- -rm -rf ./macosx/build/
- -rm -rf ./config.gypi ./config-ios.gypi ./config-android.gypi
+ -rm -rf ./config-*.gypi
-rm -rf ./android/java/build ./android/java/app/build ./android/java/lib/build
-rm -rf ./android/java/lib/src/main/jniLibs ./android/java/lib/src/main/assets
-rm -f ./android/test/features.zip
diff --git a/README.md b/README.md
index e88538aaa2..ab24baff36 100644
--- a/README.md
+++ b/README.md
@@ -36,6 +36,7 @@ To create projects, you can run:
- `make lproj`: Creates an Xcode project with platform-independent handlers for downloads and settings storage. This is what is also being built on Linux.
- `make osx run-osx`: Builds and runs the OS X application on the command line with `xcodebuild`.
- `make linux run-linux`: Builds and runs the Linux application with `make`.
+- `make test-*` Builds and runs all tests. You can specify individual tests by replacing * with their name.
Note that you can't have more than one project in Xcode open at a time since they the static library project is shared across the OS X, Linux and iOS project.
diff --git a/android/cpp/jni.cpp b/android/cpp/jni.cpp
index d3934921b6..122ec794d6 100644
--- a/android/cpp/jni.cpp
+++ b/android/cpp/jni.cpp
@@ -18,6 +18,7 @@
#include <mbgl/platform/android/log_android.hpp>
#include <mbgl/platform/event.hpp>
#include <mbgl/platform/log.hpp>
+#include <mbgl/storage/network_status.hpp>
#pragma clang diagnostic ignored "-Wunused-parameter"
@@ -423,14 +424,14 @@ nativeSetAccessToken(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jstring a
mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetAccessToken");
assert(nativeMapViewPtr != 0);
NativeMapView *nativeMapView = reinterpret_cast<NativeMapView *>(nativeMapViewPtr);
- nativeMapView->getFileSource().setAccessToken(std_string_from_jstring(env, accessToken));
+ nativeMapView->getMap().setAccessToken(std_string_from_jstring(env, accessToken));
}
jstring JNICALL nativeGetAccessToken(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) {
mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetAccessToken");
assert(nativeMapViewPtr != 0);
NativeMapView *nativeMapView = reinterpret_cast<NativeMapView *>(nativeMapViewPtr);
- return std_string_to_jstring(env, nativeMapView->getFileSource().getAccessToken());
+ return std_string_to_jstring(env, nativeMapView->getMap().getAccessToken());
}
void JNICALL nativeCancelTransitions(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) {
@@ -701,8 +702,9 @@ void JNICALL
nativeSetReachability(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jboolean status) {
mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetReachability");
assert(nativeMapViewPtr != 0);
- NativeMapView *nativeMapView = reinterpret_cast<NativeMapView *>(nativeMapViewPtr);
- nativeMapView->getFileSource().setReachability(status);
+ if (status) {
+ mbgl::NetworkStatus::Reachable();
+ }
}
}
diff --git a/android/cpp/native_map_view.cpp b/android/cpp/native_map_view.cpp
index 71db99ff8a..cd3445e24e 100644
--- a/android/cpp/native_map_view.cpp
+++ b/android/cpp/native_map_view.cpp
@@ -54,7 +54,8 @@ void log_gl_string(GLenum name, const char *label) {
NativeMapView::NativeMapView(JNIEnv *env, jobject obj_)
: mbgl::View(*this),
- fileSource(mbgl::platform::defaultCacheDatabase()),
+ fileCache(mbgl::android::cachePath + "/mbgl-cache.db"),
+ fileSource(&fileCache),
map(*this, fileSource) {
mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::NativeMapView");
@@ -140,7 +141,7 @@ void NativeMapView::notify() {
mbgl::Map &NativeMapView::getMap() { return map; }
-mbgl::CachingHTTPFileSource &NativeMapView::getFileSource() { return fileSource; }
+mbgl::DefaultFileSource &NativeMapView::getFileSource() { return fileSource; }
bool NativeMapView::inEmulator() {
// Detect if we are in emulator
diff --git a/android/mapboxgl-app.gyp b/android/mapboxgl-app.gyp
index 60ec22164d..5933db4c19 100644
--- a/android/mapboxgl-app.gyp
+++ b/android/mapboxgl-app.gyp
@@ -3,16 +3,26 @@
'../gyp/common.gypi',
],
'targets': [
- {
- 'target_name': 'androidapp',
+ { 'target_name': 'android-lib',
'product_name': 'mapbox-gl',
'type': 'shared_library',
+ 'hard_dependency': 1,
+
+ 'dependencies': [
+ '../mbgl.gyp:core',
+ '../mbgl.gyp:platform-<(platform_lib)',
+ '../mbgl.gyp:http-<(http_lib)',
+ '../mbgl.gyp:asset-<(asset_lib)',
+ '../mbgl.gyp:cache-<(cache_lib)',
+ ],
+
'sources': [
'./cpp/native_map_view.cpp',
'./cpp/jni.cpp',
],
+
'cflags_cc': [
- '-I<(boost_root)/include',
+ '<@(boost_cflags)',
],
'libraries': [
'<@(openssl_static_libs)',
@@ -50,15 +60,35 @@
'libraries': [ '<@(ldflags)' ],
}]
],
- 'dependencies': [
- '../mapboxgl.gyp:mbgl-standalone',
- '../mapboxgl.gyp:mbgl-android',
- '../mapboxgl.gyp:copy_certificate_bundle',
+ },
+
+
+ { 'target_name': 'androidapp',
+ 'type': 'none',
+ 'hard_dependency': 1,
+
+ 'variables': {
+ 'pwd': '<!(pwd)',
+ },
+
+ 'copies': [
+ {
+ 'files': [
+ '../common/ca-bundle.crt',
+ '../styles/styles'
+ ],
+ 'destination': '<(pwd)/java/lib/src/main/assets'
+ },
+ ],
+
+ 'actions': [
+ {
+ 'action_name': 'Strip dynamic library',
+ 'inputs': [ '<(PRODUCT_DIR)/lib.target/libmapbox-gl.so' ],
+ 'outputs': [ '<(pwd)/java/lib/src/main/jniLibs/$(JNIDIR)/libmapbox-gl.so' ],
+ 'action': [ '$(STRIP)', '<@(_inputs)', '-o', '<@(_outputs)' ]
+ },
],
- 'copies': [{
- 'files': [ '../styles' ],
- 'destination': '<(PRODUCT_DIR)'
- }],
},
],
}
diff --git a/android/scripts/run-build.sh b/android/scripts/run-build.sh
index 1049c82c14..a993cbdbd7 100755
--- a/android/scripts/run-build.sh
+++ b/android/scripts/run-build.sh
@@ -34,6 +34,7 @@ user_data="#!/bin/bash
export TESTMUNK_KEY=$TESTMUNK_KEY
export TESTMUNK=$TESTMUNK
export MASON_ANDROID_ABI=$MASON_ANDROID_ABI
+ export ANDROID_ABI=$MASON_ANDROID_ABI
if ./android/scripts/build-$CONFIG.sh $NAME &>../build.log; then
echo 'ANDROID BUILD PASSED'
diff --git a/bin/render.cpp b/bin/render.cpp
index ba17498c88..827f247d6c 100644
--- a/bin/render.cpp
+++ b/bin/render.cpp
@@ -9,7 +9,8 @@
#include <mbgl/platform/default/headless_view.hpp>
#include <mbgl/platform/default/headless_display.hpp>
-#include <mbgl/storage/caching_http_file_source.hpp>
+#include <mbgl/storage/default_file_source.hpp>
+#include <mbgl/storage/default/sqlite_cache.hpp>
#if __APPLE__
#include <mbgl/platform/darwin/log_nslog.hpp>
@@ -35,7 +36,7 @@ int main(int argc, char *argv[]) {
int height = 256;
double pixelRatio = 1.0;
std::string output = "out.png";
- std::string cache = "cache.sqlite";
+ std::string cache_file = "cache.sqlite";
std::vector<std::string> classes;
std::string token;
@@ -51,7 +52,7 @@ int main(int argc, char *argv[]) {
("class,c", po::value(&classes)->value_name("name"), "Class name")
("token,t", po::value(&token)->value_name("key")->default_value(token), "Mapbox access token")
("output,o", po::value(&output)->value_name("file")->default_value(output), "Output file name")
- ("cache,d", po::value(&cache)->value_name("file")->default_value(cache), "Cache database file name")
+ ("cache,d", po::value(&cache_file)->value_name("file")->default_value(cache_file), "Cache database file name")
;
try {
@@ -74,7 +75,8 @@ int main(int argc, char *argv[]) {
Log::Set<StderrLogBackend>();
#endif
- CachingHTTPFileSource fileSource(cache);
+ mbgl::SQLiteCache cache(cache_file);
+ mbgl::DefaultFileSource fileSource(&cache);
// Try to load the token from the environment.
if (!token.size()) {
@@ -84,14 +86,15 @@ int main(int argc, char *argv[]) {
}
}
- // Set access token if present
- if (token.size()) {
- fileSource.setAccessToken(std::string(token));
- }
HeadlessView view;
Map map(view, fileSource);
+ // Set access token if present
+ if (token.size()) {
+ map.setAccessToken(std::string(token));
+ }
+
map.setStyleJSON(style, ".");
map.setClasses(classes);
diff --git a/bin/render.gyp b/bin/render.gyp
index 0a41b02a8f..316a7f3ed0 100644
--- a/bin/render.gyp
+++ b/bin/render.gyp
@@ -3,51 +3,54 @@
'../gyp/common.gypi',
],
'targets': [
- {
- 'target_name': 'mbgl-render',
+ { 'target_name': 'mbgl-render',
'product_name': 'mbgl-render',
'type': 'executable',
+
+ 'dependencies': [
+ '../mbgl.gyp:core',
+ '../mbgl.gyp:platform-<(platform_lib)',
+ '../mbgl.gyp:headless-<(headless_lib)',
+ '../mbgl.gyp:http-<(http_lib)',
+ '../mbgl.gyp:asset-<(asset_lib)',
+ '../mbgl.gyp:cache-<(cache_lib)',
+ '../mbgl.gyp:copy_certificate_bundle',
+ ],
+
+ 'include_dirs': [
+ '../src',
+ ],
+
'sources': [
'./render.cpp',
],
+
'variables' : {
- 'cflags': [
- '<@(uv_cflags)',
- '<@(png_cflags)',
- '-I<(boost_root)/include',
+ 'cflags_cc': [
+ '<@(glfw3_cflags)',
+ '<@(boost_cflags)',
],
'ldflags': [
'<@(glfw3_ldflags)',
- '<@(uv_ldflags)',
- '<@(sqlite3_static_libs)',
- '<@(sqlite3_ldflags)',
- '<@(curl_ldflags)',
- '<@(png_ldflags)',
- '<@(uv_static_libs)',
- '-L<(boost_root)/lib',
+ '<@(boost_ldflags)',
'-lboost_program_options'
],
+ 'libraries': [
+ '<@(glfw3_static_libs)',
+ ],
},
+
'conditions': [
- # add libuv include path and OpenGL libs
- ['OS == "mac"',
- {
- 'xcode_settings': {
- 'OTHER_CPLUSPLUSFLAGS': ['<@(cflags)'],
- 'OTHER_LDFLAGS': ['<@(ldflags)'],
- },
- },
- {
- 'cflags': ['<@(cflags)'],
- 'libraries': ['<@(ldflags)'],
- }],
- ],
- 'include_dirs': [ '../src' ],
- 'dependencies': [
- '../mapboxgl.gyp:mbgl-standalone',
- '../mapboxgl.gyp:mbgl-headless',
- '../mapboxgl.gyp:mbgl-<(platform)',
- '../mapboxgl.gyp:copy_certificate_bundle',
+ ['OS == "mac"', {
+ 'libraries': [ '<@(libraries)' ],
+ 'xcode_settings': {
+ 'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ],
+ 'OTHER_LDFLAGS': [ '<@(ldflags)' ],
+ }
+ }, {
+ 'cflags_cc': [ '<@(cflags_cc)' ],
+ 'libraries': [ '<@(libraries)', '<@(ldflags)' ],
+ }]
],
},
],
diff --git a/configure b/configure
index 14982c2117..d00feda380 100755
--- a/configure
+++ b/configure
@@ -46,6 +46,7 @@ case ${MASON_PLATFORM} in
ZLIB_VERSION=system
BOOST_VERSION=system
NUNICODE_VERSION=1.5.1
+ LIBZIP_VERSION=0.11.2
;;
esac
@@ -72,7 +73,7 @@ LN=$'\n'
CONFIG="# Do not edit. Generated by the configure script.
{
'target_defaults': {
- 'cflags': [],
+ 'cflags%': [],
'default_configuration': 'Release',
'defines': [],
'include_dirs': [],
@@ -84,79 +85,78 @@ CONFIG="# Do not edit. Generated by the configure script.
if [ ! -z ${BOOST_VERSION} ]; then
mason install boost ${BOOST_VERSION}
- CONFIG+=" 'boost_root': '$(mason prefix boost ${BOOST_VERSION})',"$LN
+ CONFIG+=" 'boost_cflags%': $(quote_flags $(mason cflags boost ${BOOST_VERSION})),"$LN
+ CONFIG+=" 'boost_ldflags%': $(quote_flags $(mason ldflags boost ${BOOST_VERSION})),"$LN
fi
if [ ! -z ${OPENSSL_VERSION} ]; then
mason install openssl ${OPENSSL_VERSION}
- CONFIG+=" 'openssl_static_libs': $(quote_flags $(mason static_libs openssl ${OPENSSL_VERSION})),"$LN
- CONFIG+=" 'openssl_cflags': $(quote_flags $(mason cflags openssl ${OPENSSL_VERSION})),"$LN
- CONFIG+=" 'openssl_ldflags': $(quote_flags $(mason ldflags openssl ${OPENSSL_VERSION})),"$LN
+ CONFIG+=" 'openssl_static_libs%': $(quote_flags $(mason static_libs openssl ${OPENSSL_VERSION})),"$LN
+ CONFIG+=" 'openssl_cflags%': $(quote_flags $(mason cflags openssl ${OPENSSL_VERSION})),"$LN
+ CONFIG+=" 'openssl_ldflags%': $(quote_flags $(mason ldflags openssl ${OPENSSL_VERSION})),"$LN
fi
if [ ! -z ${LIBCURL_VERSION} ]; then
mason install libcurl ${LIBCURL_VERSION}
- CONFIG+=" 'curl_static_libs': $(quote_flags $(mason static_libs libcurl ${LIBCURL_VERSION})),"$LN
- CONFIG+=" 'curl_cflags': $(quote_flags $(mason cflags libcurl ${LIBCURL_VERSION})),"$LN
- CONFIG+=" 'curl_ldflags': $(quote_flags $(mason ldflags libcurl ${LIBCURL_VERSION})),"$LN
-else
- CONFIG+=" 'curl_static_libs': [],"$LN
+ CONFIG+=" 'curl_static_libs%': $(quote_flags $(mason static_libs libcurl ${LIBCURL_VERSION})),"$LN
+ CONFIG+=" 'curl_cflags%': $(quote_flags $(mason cflags libcurl ${LIBCURL_VERSION})),"$LN
+ CONFIG+=" 'curl_ldflags%': $(quote_flags $(mason ldflags libcurl ${LIBCURL_VERSION})),"$LN
fi
if [ ! -z ${GLFW_VERSION} ]; then
mason install glfw ${GLFW_VERSION}
- CONFIG+=" 'glfw3_static_libs': $(quote_flags $(mason static_libs glfw ${GLFW_VERSION})),"$LN
- CONFIG+=" 'glfw3_cflags': $(quote_flags $(mason cflags glfw ${GLFW_VERSION})),"$LN
- CONFIG+=" 'glfw3_ldflags': $(quote_flags $(mason ldflags glfw ${GLFW_VERSION})),"$LN
+ CONFIG+=" 'glfw3_static_libs%': $(quote_flags $(mason static_libs glfw ${GLFW_VERSION})),"$LN
+ CONFIG+=" 'glfw3_cflags%': $(quote_flags $(mason cflags glfw ${GLFW_VERSION})),"$LN
+ CONFIG+=" 'glfw3_ldflags%': $(quote_flags $(mason ldflags glfw ${GLFW_VERSION})),"$LN
fi
if [ ! -z ${LIBPNG_VERSION} ]; then
mason install libpng ${LIBPNG_VERSION}
- CONFIG+=" 'png_static_libs': $(quote_flags $(mason static_libs libpng ${LIBPNG_VERSION})),"$LN
- CONFIG+=" 'png_cflags': $(quote_flags $(mason cflags libpng ${LIBPNG_VERSION})),"$LN
- CONFIG+=" 'png_ldflags': $(quote_flags $(mason ldflags libpng ${LIBPNG_VERSION})),"$LN
+ CONFIG+=" 'png_static_libs%': $(quote_flags $(mason static_libs libpng ${LIBPNG_VERSION})),"$LN
+ CONFIG+=" 'png_cflags%': $(quote_flags $(mason cflags libpng ${LIBPNG_VERSION})),"$LN
+ CONFIG+=" 'png_ldflags%': $(quote_flags $(mason ldflags libpng ${LIBPNG_VERSION})),"$LN
fi
if [ ! -z ${LIBJPEG_VERSION} ]; then
mason install jpeg ${LIBJPEG_VERSION}
- CONFIG+=" 'jpeg_static_libs': $(quote_flags $(mason static_libs jpeg ${LIBJPEG_VERSION})),"$LN
- CONFIG+=" 'jpeg_cflags': $(quote_flags $(mason cflags jpeg ${LIBJPEG_VERSION})),"$LN
- CONFIG+=" 'jpeg_ldflags': $(quote_flags $(mason ldflags jpeg ${LIBJPEG_VERSION})),"$LN
+ CONFIG+=" 'jpeg_static_libs%': $(quote_flags $(mason static_libs jpeg ${LIBJPEG_VERSION})),"$LN
+ CONFIG+=" 'jpeg_cflags%': $(quote_flags $(mason cflags jpeg ${LIBJPEG_VERSION})),"$LN
+ CONFIG+=" 'jpeg_ldflags%': $(quote_flags $(mason ldflags jpeg ${LIBJPEG_VERSION})),"$LN
fi
if [ ! -z ${SQLITE_VERSION} ]; then
mason install sqlite ${SQLITE_VERSION}
- CONFIG+=" 'sqlite3_static_libs': $(quote_flags $(mason static_libs sqlite ${SQLITE_VERSION})),"$LN
- CONFIG+=" 'sqlite3_cflags': $(quote_flags $(mason cflags sqlite ${SQLITE_VERSION})),"$LN
- CONFIG+=" 'sqlite3_ldflags': $(quote_flags $(mason ldflags sqlite ${SQLITE_VERSION})),"$LN
+ CONFIG+=" 'sqlite3_static_libs%': $(quote_flags $(mason static_libs sqlite ${SQLITE_VERSION})),"$LN
+ CONFIG+=" 'sqlite3_cflags%': $(quote_flags $(mason cflags sqlite ${SQLITE_VERSION})),"$LN
+ CONFIG+=" 'sqlite3_ldflags%': $(quote_flags $(mason ldflags sqlite ${SQLITE_VERSION})),"$LN
fi
if [ ! -z ${LIBUV_VERSION} ]; then
mason install libuv ${LIBUV_VERSION}
- CONFIG+=" 'uv_static_libs': $(quote_flags $(mason static_libs libuv ${LIBUV_VERSION})),"$LN
- CONFIG+=" 'uv_cflags': $(quote_flags $(mason cflags libuv ${LIBUV_VERSION})),"$LN
- CONFIG+=" 'uv_ldflags': $(quote_flags $(mason ldflags libuv ${LIBUV_VERSION})),"$LN
+ CONFIG+=" 'uv_static_libs%': $(quote_flags $(mason static_libs libuv ${LIBUV_VERSION})),"$LN
+ CONFIG+=" 'uv_cflags%': $(quote_flags $(mason cflags libuv ${LIBUV_VERSION})),"$LN
+ CONFIG+=" 'uv_ldflags%': $(quote_flags $(mason ldflags libuv ${LIBUV_VERSION})),"$LN
fi
if [ ! -z ${ZLIB_VERSION} ]; then
mason install zlib ${ZLIB_VERSION}
- CONFIG+=" 'zlib_static_libs': $(quote_flags $(mason static_libs zlib ${ZLIB_VERSION})),"$LN
- CONFIG+=" 'zlib_cflags': $(quote_flags $(mason cflags zlib ${ZLIB_VERSION})),"$LN
- CONFIG+=" 'zlib_ldflags': $(quote_flags $(mason ldflags zlib ${ZLIB_VERSION})),"$LN
+ CONFIG+=" 'zlib_static_libs%': $(quote_flags $(mason static_libs zlib ${ZLIB_VERSION})),"$LN
+ CONFIG+=" 'zlib_cflags%': $(quote_flags $(mason cflags zlib ${ZLIB_VERSION})),"$LN
+ CONFIG+=" 'zlib_ldflags%': $(quote_flags $(mason ldflags zlib ${ZLIB_VERSION})),"$LN
fi
if [ ! -z ${NUNICODE_VERSION} ]; then
mason install nunicode ${NUNICODE_VERSION}
- CONFIG+=" 'nu_static_libs': $(quote_flags $(mason static_libs nunicode ${NUNICODE_VERSION})),"$LN
- CONFIG+=" 'nu_cflags': $(quote_flags $(mason cflags nunicode ${NUNICODE_VERSION})),"$LN
- CONFIG+=" 'nu_ldflags': $(quote_flags $(mason ldflags nunicode ${NUNICODE_VERSION})),"$LN
+ CONFIG+=" 'nu_static_libs%': $(quote_flags $(mason static_libs nunicode ${NUNICODE_VERSION})),"$LN
+ CONFIG+=" 'nu_cflags%': $(quote_flags $(mason cflags nunicode ${NUNICODE_VERSION})),"$LN
+ CONFIG+=" 'nu_ldflags%': $(quote_flags $(mason ldflags nunicode ${NUNICODE_VERSION})),"$LN
fi
if [ ! -z ${LIBZIP_VERSION} ]; then
mason install libzip ${LIBZIP_VERSION}
- CONFIG+=" 'zip_static_libs': $(quote_flags $(mason static_libs libzip ${LIBZIP_VERSION})),"$LN
- CONFIG+=" 'zip_cflags': $(quote_flags $(mason cflags libzip ${LIBZIP_VERSION})),"$LN
- CONFIG+=" 'zip_ldflags': $(quote_flags $(mason ldflags libzip ${LIBZIP_VERSION})),"$LN
+ CONFIG+=" 'zip_static_libs%': $(quote_flags $(mason static_libs libzip ${LIBZIP_VERSION})),"$LN
+ CONFIG+=" 'zip_cflags%': $(quote_flags $(mason cflags libzip ${LIBZIP_VERSION})),"$LN
+ CONFIG+=" 'zip_ldflags%': $(quote_flags $(mason ldflags libzip ${LIBZIP_VERSION})),"$LN
fi
CONFIG+=" }
@@ -164,5 +164,3 @@ CONFIG+=" }
"
echo "${CONFIG}" > ${CONFIG_FILE}
-
-cat ${CONFIG_FILE}
diff --git a/gyp/asset-fs.gypi b/gyp/asset-fs.gypi
new file mode 100644
index 0000000000..0606eaf7f8
--- /dev/null
+++ b/gyp/asset-fs.gypi
@@ -0,0 +1,52 @@
+{
+ 'targets': [
+ { 'target_name': 'asset-fs',
+ 'product_name': 'mbgl-asset-fs',
+ 'type': 'static_library',
+ 'standalone_static_library': 1,
+ 'hard_dependency': 1,
+
+ 'sources': [
+ '../platform/default/asset_request_fs.cpp',
+ ],
+
+ 'include_dirs': [
+ '../include',
+ ],
+
+ 'variables': {
+ 'cflags_cc': [
+ '<@(uv_cflags)',
+ '<@(boost_cflags)',
+ ],
+ 'ldflags': [
+ '<@(uv_ldflags)',
+ ],
+ 'libraries': [
+ '<@(uv_static_libs)',
+ ],
+ },
+
+ 'conditions': [
+ ['OS == "mac"', {
+ 'xcode_settings': {
+ 'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ],
+ },
+ }, {
+ 'cflags_cc': [ '<@(cflags_cc)' ],
+ }],
+ ],
+
+ 'link_settings': {
+ 'conditions': [
+ ['OS == "mac"', {
+ 'libraries': [ '<@(libraries)' ],
+ 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)' ] }
+ }, {
+ 'libraries': [ '<@(libraries)', '<@(ldflags)' ],
+ }]
+ ],
+ },
+ },
+ ],
+}
diff --git a/gyp/asset-zip.gypi b/gyp/asset-zip.gypi
new file mode 100644
index 0000000000..25df2a3d8b
--- /dev/null
+++ b/gyp/asset-zip.gypi
@@ -0,0 +1,62 @@
+{
+ 'targets': [
+ { 'target_name': 'asset-zip',
+ 'product_name': 'mbgl-asset-zip',
+ 'type': 'static_library',
+ 'standalone_static_library': 1,
+ 'hard_dependency': 1,
+
+ 'sources': [
+ '../platform/default/asset_request_zip.cpp',
+ '../platform/default/uv_zip.c',
+ ],
+
+ 'include_dirs': [
+ '../include',
+ ],
+
+ 'variables': {
+ 'cflags': [
+ '<@(uv_cflags)',
+ '<@(zip_cflags)',
+ ],
+ 'cflags_cc': [
+ '<@(uv_cflags)',
+ '<@(zip_cflags)',
+ '<@(boost_cflags)',
+ ],
+ 'ldflags': [
+ '<@(uv_ldflags)',
+ '<@(zip_ldflags)',
+ ],
+ 'libraries': [
+ '<@(uv_static_libs)',
+ '<@(zip_static_libs)',
+ ],
+ },
+
+ 'conditions': [
+ ['OS == "mac"', {
+ 'xcode_settings': {
+ 'OTHER_CFLAGS': [ '<@(cflags)' ],
+ 'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ],
+ },
+ }, {
+ 'cflags': [ '<@(cflags)' ],
+ 'cflags_cc': [ '<@(cflags_cc)' ],
+ }],
+ ],
+
+ 'link_settings': {
+ 'conditions': [
+ ['OS == "mac"', {
+ 'libraries': [ '<@(libraries)' ],
+ 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)' ] }
+ }, {
+ 'libraries': [ '<@(libraries)', '<@(ldflags)' ],
+ }]
+ ],
+ },
+ },
+ ],
+}
diff --git a/gyp/cache-sqlite.gypi b/gyp/cache-sqlite.gypi
new file mode 100644
index 0000000000..53af8dab59
--- /dev/null
+++ b/gyp/cache-sqlite.gypi
@@ -0,0 +1,64 @@
+{
+ 'targets': [
+ { 'target_name': 'cache-sqlite',
+ 'product_name': 'mbgl-cache-sqlite',
+ 'type': 'static_library',
+ 'standalone_static_library': 1,
+ 'hard_dependency': 1,
+
+ 'sources': [
+ '../platform/default/sqlite_cache.cpp',
+ '../platform/default/sqlite3.hpp',
+ '../platform/default/sqlite3.cpp',
+ '../platform/default/compression.hpp',
+ '../platform/default/compression.cpp',
+ ],
+
+ 'include_dirs': [
+ '../include',
+ ],
+
+ 'variables': {
+ 'cflags_cc': [
+ '<@(uv_cflags)',
+ '<@(sqlite3_cflags)',
+ ],
+ 'ldflags': [
+ '<@(uv_ldflags)',
+ '<@(sqlite3_ldflags)',
+ '<@(zlib_ldflags)',
+ ],
+ 'libraries': [
+ '<@(uv_static_libs)',
+ '<@(sqlite3_static_libs)',
+ '<@(zlib_static_libs)',
+ ],
+ },
+
+ 'conditions': [
+ ['OS == "mac"', {
+ 'xcode_settings': {
+ 'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ],
+ },
+ }, {
+ 'cflags_cc': [ '<@(cflags_cc)' ],
+ }],
+ ],
+
+ 'link_settings': {
+ 'conditions': [
+ ['OS == "mac"', {
+ 'libraries': [ '<@(libraries)' ],
+ 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)' ] }
+ }, {
+ 'libraries': [ '<@(libraries)', '<@(ldflags)' ],
+ }]
+ ],
+ },
+
+ 'direct_dependent_settings': {
+ 'include_dirs': [ '../include' ],
+ },
+ },
+ ],
+}
diff --git a/gyp/common.gypi b/gyp/common.gypi
index 92cfb2ab18..7cf13fff6e 100644
--- a/gyp/common.gypi
+++ b/gyp/common.gypi
@@ -1,19 +1,18 @@
{
'variables': {
'install_prefix%': '',
- 'standalone_product_dir':'<!@(pwd)/build'
},
'target_defaults': {
'default_configuration': 'Release',
'conditions': [
['OS=="mac"', {
'xcode_settings': {
- 'MACOSX_DEPLOYMENT_TARGET':'10.9',
+ 'MACOSX_DEPLOYMENT_TARGET': '10.9',
'CLANG_CXX_LIBRARY': 'libc++',
- 'CLANG_CXX_LANGUAGE_STANDARD':'c++11',
+ 'CLANG_CXX_LANGUAGE_STANDARD': 'c++11',
'GCC_VERSION': 'com.apple.compilers.llvm.clang.1_0',
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
- 'GCC_ENABLE_CPP_RTTI':'YES',
+ 'GCC_ENABLE_CPP_RTTI': 'YES',
'OTHER_CPLUSPLUSFLAGS': [
'-Werror',
'-Wall',
@@ -26,9 +25,23 @@
'GCC_WARN_PEDANTIC': 'YES',
'GCC_WARN_UNINITIALIZED_AUTOS': 'YES_AGGRESSIVE',
},
+ }, {
+ 'cflags_cc': [
+ '-std=c++11',
+ '-Werror',
+ '-Wall',
+ '-Wextra',
+ '-Wshadow',
+ '-Wno-variadic-macros',
+ '-Wno-error=unused-parameter',
+ '-Wno-unknown-warning-option',
+ '-frtti',
+ '-fexceptions',
+ ],
}],
['OS=="linux"', {
- 'cflags_cc': ['-Wno-unknown-pragmas', # We are using '#pragma mark', but it is only available on Darwin.
+ 'cflags_cc': [
+ '-Wno-unknown-pragmas', # We are using '#pragma mark', but it is only available on Darwin.
],
}],
],
@@ -38,14 +51,31 @@
['OS=="mac"', {
'xcode_settings': {
'OTHER_CPLUSPLUSFLAGS': [ '-fPIC' ],
+ 'SKIP_INSTALL': 'YES',
},
}, {
'cflags_cc': [ '-fPIC' ],
- }]
+ }],
+ ['host == "ios"', {
+ 'xcode_settings': {
+ 'SDKROOT': 'iphoneos',
+ 'SUPPORTED_PLATFORMS': 'iphonesimulator iphoneos',
+ 'IPHONEOS_DEPLOYMENT_TARGET': '7.0',
+ 'TARGETED_DEVICE_FAMILY': '1,2',
+ 'GCC_VERSION': 'com.apple.compilers.llvm.clang.1_0',
+ 'CODE_SIGN_IDENTITY': 'iPhone Developer',
+ },
+ 'configurations': {
+ 'Release': {
+ 'xcode_settings': {
+ 'ARCHS': [ "armv7", "armv7s", "arm64", "i386", "x86_64" ],
+ },
+ },
+ },
+ }],
],
}],
],
- 'cflags_cc': [ '-std=c++11', '-Werror', '-Wall', '-Wextra', '-Wshadow', '-frtti', '-fexceptions' ],
'configurations': {
'Debug': {
'cflags_cc': [ '-g', '-O0', '-fno-omit-frame-pointer','-fwrapv', '-fstack-protector-all', '-fno-common' ],
@@ -59,12 +89,12 @@
}
},
'Release': {
- 'cflags_cc': [ '-O3' ],
+ 'cflags_cc': [ '-g', '-O3' ],
'defines': [ 'NDEBUG' ],
'xcode_settings': {
'GCC_OPTIMIZATION_LEVEL': '3',
- 'GCC_GENERATE_DEBUGGING_SYMBOLS': 'NO',
- 'DEAD_CODE_STRIPPING': 'YES',
+ 'GCC_GENERATE_DEBUGGING_SYMBOLS': 'YES',
+ 'DEAD_CODE_STRIPPING': 'NO',
'GCC_INLINES_ARE_PRIVATE_EXTERN': 'NO'
}
},
diff --git a/gyp/mbgl-core.gypi b/gyp/core.gypi
index aa30c5b4dd..9a3b07d0bb 100644
--- a/gyp/mbgl-core.gypi
+++ b/gyp/core.gypi
@@ -1,26 +1,17 @@
{
'targets': [
- { 'target_name': 'mbgl-core',
+ { 'target_name': 'core',
'product_name': 'mbgl-core',
'type': 'static_library',
'standalone_static_library': 1,
'hard_dependency': 1,
'dependencies': [
- 'shaders',
- 'version',
+ 'shaders',
+ 'version',
],
- 'variables': {
- 'cflags_cc': [
- '<@(uv_cflags)',
- '<@(sqlite3_cflags)',
- '<@(zlib_cflags)',
- '-I<(boost_root)/include',
- ],
- 'cflags': [
- '<@(uv_cflags)','-fPIC'
- ],
- },
+
'sources': [
+ '<!@(find src -name "*.hpp")',
'<!@(find src -name "*.cpp")',
'<!@(find src -name "*.c")',
'<!@(find src -name "*.h")',
@@ -29,67 +20,57 @@
'<!@(find src -name "*.glsl")',
'bin/style.json'
],
+
'include_dirs': [
'../include',
'../src',
],
+
+ 'variables': {
+ 'cflags_cc': [
+ '<@(uv_cflags)',
+ '<@(boost_cflags)',
+ ],
+ 'cflags': [
+ '<@(uv_cflags)',
+ '-fPIC'
+ ],
+ 'ldflags': [
+ '<@(uv_ldflags)',
+ ],
+ 'libraries': [
+ '<@(uv_static_libs)',
+ ],
+ },
+
'conditions': [
['OS == "mac"', {
'xcode_settings': {
'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ],
'OTHER_CFLAGS': [ '<@(cflags)' ],
- 'SKIP_INSTALL': 'YES'
},
}, {
'cflags_cc': [ '<@(cflags_cc)' ],
'cflags': [ '<@(cflags)' ],
}]
- ]
- },
- {
- 'target_name': 'mbgl-standalone',
- 'type': 'none',
- 'hard_dependency': 1,
- 'dependencies': [
- 'mbgl-core'
],
- 'variables': {
- 'core_lib':'<(PRODUCT_DIR)/libmbgl-core.a',
- 'standalone_lib':'<(standalone_product_dir)/libmbgl.a'
- },
- 'direct_dependent_settings': {
- 'include_dirs': [
- '../include',
- ],
+
+ 'link_settings': {
'conditions': [
['OS == "mac"', {
- 'xcode_settings': {
- 'OTHER_LDFLAGS': [ '<(standalone_lib)' ],
- }
+ 'libraries': [ '<@(libraries)' ],
+ 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)' ] }
}, {
- 'ldflags': [ '<(standalone_lib)' ],
+ 'libraries': [ '<@(libraries)', '<@(ldflags)' ],
}]
],
},
- 'actions': [
- {
- 'action_name': 'build standalone core lib',
- 'inputs': [
- '<(core_lib)'
- ],
- 'outputs': [
- '<(standalone_lib)'
- ],
- 'action': [
- './gyp/merge_static_libs.py',
- '<(standalone_lib)',
- '<@(uv_static_libs)',
- '<@(curl_static_libs)',
- '<@(sqlite3_static_libs)',
- '<(core_lib)'
- ],
- }
- ]
- }
+
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '../include',
+ ],
+ },
+ },
]
}
diff --git a/gyp/headless-cgl.gypi b/gyp/headless-cgl.gypi
new file mode 100644
index 0000000000..bcb677c6ed
--- /dev/null
+++ b/gyp/headless-cgl.gypi
@@ -0,0 +1,18 @@
+{
+ 'targets': [
+ { 'target_name': 'headless-cgl',
+ 'product_name': 'mbgl-headless-cgl',
+ 'type': 'static_library',
+ 'standalone_static_library': 1,
+
+ 'sources': [
+ '../platform/default/headless_view.cpp',
+ '../platform/default/headless_display.cpp',
+ ],
+
+ 'include_dirs': [
+ '../include',
+ ],
+ },
+ ],
+}
diff --git a/gyp/headless-glx.gypi b/gyp/headless-glx.gypi
new file mode 100644
index 0000000000..06062be75f
--- /dev/null
+++ b/gyp/headless-glx.gypi
@@ -0,0 +1,18 @@
+{
+ 'targets': [
+ { 'target_name': 'headless-glx',
+ 'product_name': 'mbgl-headless-glx',
+ 'type': 'static_library',
+ 'standalone_static_library': 1,
+
+ 'sources': [
+ '../platform/default/headless_view.cpp',
+ '../platform/default/headless_display.cpp',
+ ],
+
+ 'include_dirs': [
+ '../include',
+ ],
+ },
+ ],
+}
diff --git a/gyp/http-curl.gypi b/gyp/http-curl.gypi
new file mode 100644
index 0000000000..87edff2492
--- /dev/null
+++ b/gyp/http-curl.gypi
@@ -0,0 +1,55 @@
+{
+ 'targets': [
+ { 'target_name': 'http-curl',
+ 'product_name': 'mbgl-http-curl',
+ 'type': 'static_library',
+ 'standalone_static_library': 1,
+ 'hard_dependency': 1,
+
+ 'sources': [
+ '../platform/default/http_request_curl.cpp',
+ ],
+
+ 'include_dirs': [
+ '../include',
+ ],
+
+ 'variables': {
+ 'cflags_cc': [
+ '<@(uv_cflags)',
+ '<@(curl_cflags)',
+ '<@(boost_cflags)',
+ ],
+ 'ldflags': [
+ '<@(uv_ldflags)',
+ '<@(curl_ldflags)',
+ ],
+ 'libraries': [
+ '<@(uv_static_libs)',
+ '<@(curl_static_libs)',
+ ],
+ },
+
+ 'conditions': [
+ ['OS == "mac"', {
+ 'xcode_settings': {
+ 'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ],
+ },
+ }, {
+ 'cflags_cc': [ '<@(cflags_cc)' ],
+ }],
+ ],
+
+ 'link_settings': {
+ 'conditions': [
+ ['OS == "mac"', {
+ 'libraries': [ '<@(libraries)' ],
+ 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)' ] }
+ }, {
+ 'libraries': [ '<@(libraries)', '<@(ldflags)' ],
+ }]
+ ],
+ },
+ },
+ ],
+}
diff --git a/gyp/http-nsurl.gypi b/gyp/http-nsurl.gypi
new file mode 100644
index 0000000000..4205f59d81
--- /dev/null
+++ b/gyp/http-nsurl.gypi
@@ -0,0 +1,43 @@
+{
+ 'targets': [
+ { 'target_name': 'http-nsurl',
+ 'product_name': 'mbgl-http-nsurl',
+ 'type': 'static_library',
+ 'standalone_static_library': 1,
+ 'hard_dependency': 1,
+
+ 'sources': [
+ '../platform/darwin/http_request_nsurl.mm',
+ ],
+
+ 'include_dirs': [
+ '../include',
+ ],
+
+ 'variables': {
+ 'cflags_cc': [
+ '<@(uv_cflags)',
+ ],
+ 'ldflags': [
+ '-framework Foundation', # For NSURLRequest
+ '<@(uv_ldflags)',
+ ],
+ 'libraries': [
+ '<@(uv_static_libs)',
+ ],
+ },
+
+ 'xcode_settings': {
+ 'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ],
+ 'CLANG_ENABLE_OBJC_ARC': 'NO',
+ },
+
+ 'link_settings': {
+ 'libraries': [ '<@(libraries)' ],
+ 'xcode_settings': {
+ 'OTHER_LDFLAGS': [ '<@(ldflags)' ],
+ },
+ },
+ },
+ ],
+}
diff --git a/gyp/install.gypi b/gyp/install.gypi
index 1c5066a9c0..4ed32eea14 100644
--- a/gyp/install.gypi
+++ b/gyp/install.gypi
@@ -6,66 +6,45 @@
'type': 'none',
'hard_dependency': 1,
'dependencies': [
- 'mbgl-core',
- 'mbgl-standalone',
- 'mbgl-headless',
- 'mbgl-<(platform)',
+ 'core',
+ 'platform-<(platform_lib)',
+ 'http-<(http_lib)',
+ 'asset-<(asset_lib)',
+ 'cache-<(cache_lib)',
+ 'headless-<(headless_lib)',
+ 'standalone',
],
+
'copies': [
- { 'files': [ '<(standalone_product_dir)/libmbgl.a' ], 'destination': '<(install_prefix)/lib' },
- { 'files': [ '<(PRODUCT_DIR)/libmbgl-core.a' ], 'destination': '<(install_prefix)/lib' },
- { 'files': [ '<(PRODUCT_DIR)/libmbgl-headless.a' ], 'destination': '<(install_prefix)/lib' },
- { 'files': [ '<(PRODUCT_DIR)/libmbgl-<(platform).a' ], 'destination': '<(install_prefix)/lib' },
- { 'files': [ '../include/mbgl' ], 'destination': '<(install_prefix)/include' },
+ { 'files': [ '<(PRODUCT_DIR)/libmbgl.a' ], 'destination': '<(install_prefix)/lib' },
+ { 'files': [ '<(SHARED_INTERMEDIATE_DIR)/include/mbgl/util/version.hpp' ], 'destination': '<(install_prefix)/include/mbgl/util' },
],
- 'variables': {
- 'conditions': [
- ['OS == "linux"', {
- 'other_ldflags': [
- '<@(nu_static_libs)',
- '<@(png_static_libs)',
- '<@(jpeg_static_libs)',
- '<@(glfw3_static_libs)',
- '<@(glfw3_ldflags)',
- ]
- }, {
- 'other_ldflags': [ ]
- }]
- ],
- },
+
'actions': [
- { 'action_name': 'mbgl-config',
- 'inputs': [
- '../utils/mbgl-config/mbgl-config.template.sh',
- '../utils/mbgl-config/build.sh',
- ],
- 'outputs': [
- '<(install_prefix)/bin/mbgl-config',
- ],
- 'action': [
- './utils/mbgl-config/build.sh',
- '<(install_prefix)',
- '<(platform)',
- '<@(sqlite3_static_libs)',
- '<@(sqlite3_ldflags)',
- '<@(curl_ldflags)',
- '<@(png_ldflags)',
- '<@(other_ldflags)'
- ]
- }
+ {
+ 'action_name': 'Copy header files',
+ 'inputs': [ '../include/mbgl/mbgl.hpp' ],
+ 'outputs': [ '../include/mbgl/mbgl.hpp' ],
+ 'action': [ 'cp', '-r', 'include', '<(install_prefix)/' ]
+ },
+
+ { 'action_name': 'mbgl-config',
+ 'inputs': [
+ '../utils/mbgl-config/mbgl-config.template.sh',
+ '../utils/mbgl-config/build.sh',
+ ],
+ 'outputs': [
+ '<(install_prefix)/bin/mbgl-config',
+ ],
+ 'action': [
+ './utils/mbgl-config/build.sh',
+ '<(install_prefix)',
+ '<(PRODUCT_DIR)/libmbgl.a.ldflags',
+ ]
+ }
]
},
- { 'target_name': 'copy_version',
- 'type': 'none',
- 'hard_dependency': 1,
- 'dependencies': [
- 'install',
- ],
- 'copies': [
- { 'files': [ '<(SHARED_INTERMEDIATE_DIR)/include/mbgl/util/version.hpp' ], 'destination': '<(install_prefix)/include/mbgl/util' },
- ],
- }
- ]
+ ],
}],
],
}
diff --git a/gyp/link.py b/gyp/link.py
new file mode 100755
index 0000000000..f8d98074e2
--- /dev/null
+++ b/gyp/link.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+
+import sys
+import os
+from merge_static_libs import MergeLibs
+
+args = sys.argv[1:]
+
+out_lib = ''
+in_libs = []
+flags = []
+
+i = 0
+l = len(args)
+while i < l:
+ arg = args[i]
+
+ if arg[0:2] == '-l':
+ flags.append(arg)
+ elif arg[0:2] == '-L':
+ flags.append(arg)
+ elif arg == '-arch':
+ i += 1
+ elif arg == '-framework':
+ i += 1
+ flags.append(arg + ' ' + args[i])
+ elif arg == '-o':
+ i += 1
+ out_lib = args[i]
+ elif arg[0] != '-':
+ in_libs.append(arg)
+ else:
+ print 'Ignored linker directive: ' + arg
+
+ i += 1
+
+flags.reverse()
+unique_flags = []
+for flag in flags:
+ if flag not in unique_flags:
+ unique_flags.append(flag)
+unique_flags.reverse()
+
+with open(out_lib + '.ldflags', 'w+') as f:
+ f.write(' '.join(unique_flags));
+
+MergeLibs(in_libs, out_lib)
diff --git a/gyp/load.gypi b/gyp/load.gypi
new file mode 100644
index 0000000000..3d666ac938
--- /dev/null
+++ b/gyp/load.gypi
@@ -0,0 +1,6 @@
+{
+ 'conditions': [
+ ['platform_library == "mbglosx"', {
+ }],
+ ],
+} \ No newline at end of file
diff --git a/gyp/mbgl-headless.gypi b/gyp/mbgl-headless.gypi
deleted file mode 100644
index a96ec24c9e..0000000000
--- a/gyp/mbgl-headless.gypi
+++ /dev/null
@@ -1,35 +0,0 @@
-{
- 'targets': [
- { 'target_name': 'mbgl-headless',
- 'product_name': 'mbgl-headless',
- 'type': 'static_library',
- 'standalone_static_library': 1,
- 'variables': {
- 'cflags_cc': [
- '<@(uv_cflags)',
- ],
- 'cflags': [
- '<@(uv_cflags)',
- ],
- },
- 'include_dirs': [
- '../include',
- ],
- 'conditions': [
- ['OS == "mac"', {
- 'xcode_settings': {
- 'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ],
- 'OTHER_CFLAGS': [ '<@(cflags)' ],
- }
- }, {
- 'cflags_cc': [ '<@(cflags_cc)' ],
- 'cflags': [ '<@(cflags)' ],
- }]
- ],
- 'sources': [
- '../platform/default/headless_view.cpp',
- '../platform/default/headless_display.cpp',
- ],
- },
- ],
-}
diff --git a/gyp/mbgl-ios.gypi b/gyp/mbgl-ios.gypi
deleted file mode 100644
index b1a55c8176..0000000000
--- a/gyp/mbgl-ios.gypi
+++ /dev/null
@@ -1,65 +0,0 @@
-{
- 'target_defaults': {
- 'target_conditions': [
- ['_type == "static_library"', {
- 'xcode_settings': {
- 'SDKROOT': 'iphoneos',
- 'SUPPORTED_PLATFORMS': 'iphonesimulator iphoneos',
- 'CLANG_CXX_LIBRARY': 'libc++',
- 'CLANG_CXX_LANGUAGE_STANDARD':'c++11',
- 'IPHONEOS_DEPLOYMENT_TARGET':'7.0',
- 'TARGETED_DEVICE_FAMILY': '1,2',
- 'GCC_VERSION': 'com.apple.compilers.llvm.clang.1_0',
- 'CLANG_ENABLE_OBJC_ARC': 'YES',
- 'CODE_SIGN_IDENTITY': 'iPhone Developer',
- 'SKIP_INSTALL': 'YES'
- },
- 'configurations': {
- 'Release': {
- 'xcode_settings': {
- 'ARCHS': [ "armv7", "armv7s", "arm64", "i386", "x86_64" ],
- },
- },
- },
- }],
- ],
- },
- 'targets': [
- { 'target_name': 'mbgl-ios',
- 'product_name': 'mbgl-ios',
- 'type': 'static_library',
- 'standalone_static_library': 1,
- 'hard_dependency': 1,
- 'dependencies': [
- 'version',
- ],
- 'sources': [
- '../platform/ios/cache_database_library.mm',
- '../platform/darwin/log_nslog.mm',
- '../platform/darwin/string_nsstring.mm',
- '../platform/darwin/http_request_baton_cocoa.mm',
- '../platform/darwin/application_root.mm',
- '../platform/darwin/image.mm',
- '../platform/default/asset_request_libuv.cpp',
- ],
- 'include_dirs': [
- '../include',
- '../src',
- ],
- 'xcode_settings': {
- 'OTHER_CPLUSPLUSFLAGS': [ '<@(uv_cflags)' ],
- },
- 'direct_dependent_settings': {
- 'include_dirs': [
- '../include',
- ],
- 'xcode_settings': {
- 'OTHER_LDFLAGS': [
- '-framework ImageIO',
- '-framework MobileCoreServices',
- ],
- },
- },
- },
- ],
-}
diff --git a/gyp/mbgl-platform.gypi b/gyp/mbgl-platform.gypi
deleted file mode 100644
index 08fa5f821c..0000000000
--- a/gyp/mbgl-platform.gypi
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- 'conditions': [
- ['platform == "osx"', {
- 'includes': ['mbgl-osx.gypi'],
- 'variables': { 'platform_library': 'mbgl-osx' },
- }],
- ['platform == "ios"', {
- 'includes': ['mbgl-ios.gypi'],
- 'variables': { 'platform_library': 'mbgl-ios' },
- }],
- ['platform == "linux"', {
- 'includes': ['mbgl-linux.gypi'],
- 'variables': { 'platform_library': 'mbgl-linux' },
- }],
- ['platform == "android"', {
- 'includes': ['mbgl-android.gypi'],
- 'variables': { 'platform_library': 'mbgl-android' },
- }],
- ],
-}
diff --git a/gyp/none.gypi b/gyp/none.gypi
new file mode 100644
index 0000000000..ce748bfe6f
--- /dev/null
+++ b/gyp/none.gypi
@@ -0,0 +1,20 @@
+{
+ 'targets': [
+ { 'target_name': 'http-none',
+ 'product_name': 'mbgl-http-none',
+ 'type': 'none',
+ },
+ { 'target_name': 'asset-none',
+ 'product_name': 'mbgl-asset-none',
+ 'type': 'none',
+ },
+ { 'target_name': 'cache-none',
+ 'product_name': 'mbgl-cache-none',
+ 'type': 'none',
+ },
+ { 'target_name': 'headless-none',
+ 'product_name': 'mbgl-headless-none',
+ 'type': 'none',
+ },
+ ],
+}
diff --git a/gyp/mbgl-android.gypi b/gyp/platform-android.gypi
index 85115910cd..cbd855e9c2 100644
--- a/gyp/mbgl-android.gypi
+++ b/gyp/platform-android.gypi
@@ -1,84 +1,76 @@
{
'targets': [
- { 'target_name': 'mbgl-android',
- 'product_name': 'mbgl-android',
+ { 'target_name': 'platform-android',
+ 'product_name': 'mbgl-platform-android',
'type': 'static_library',
'standalone_static_library': 1,
'hard_dependency': 1,
'dependencies': [
'version',
],
+
+ 'sources': [
+ '../platform/android/log_android.cpp',
+ '../platform/default/string_stdlib.cpp',
+ '../platform/default/image.cpp',
+ '../platform/default/image_reader.cpp',
+ '../platform/default/png_reader.cpp',
+ '../platform/default/jpeg_reader.cpp',
+ ],
+
'variables': {
'cflags_cc': [
'<@(png_cflags)',
'<@(jpeg_cflags)',
'<@(uv_cflags)',
- '<@(curl_cflags)',
- '<@(nu_cflags)',
- '<@(zip_cflags)',
- '<@(openssl_cflags)',
- '-I<(boost_root)/include',
- ],
- 'cflags': [
- '<@(uv_cflags)',
'<@(nu_cflags)',
+ '<@(boost_cflags)',
],
'ldflags': [
'<@(png_ldflags)',
'<@(jpeg_ldflags)',
'<@(uv_ldflags)',
- '<@(curl_ldflags)',
'<@(nu_ldflags)',
- '<@(zip_ldflags)',
],
- },
- 'sources': [
- '../platform/android/cache_database_data.cpp',
- '../platform/android/log_android.cpp',
- '../platform/android/asset_request_libzip.cpp',
- '../platform/default/string_stdlib.cpp',
- '../platform/default/http_request_baton_curl.cpp',
- '../platform/default/image.cpp',
- '../platform/default/image_reader.cpp',
- '../platform/default/png_reader.cpp',
- '../platform/default/jpeg_reader.cpp',
- ],
- 'include_dirs': [
- '../include',
- '../src',
- ],
- 'link_settings': {
'libraries': [
'<@(png_static_libs)',
'<@(jpeg_static_libs)',
- '<@(nu_static_libs)'
+ '<@(uv_static_libs)',
+ '<@(nu_static_libs)',
],
},
+
+ 'include_dirs': [
+ '../include',
+ '../src',
+ ],
+
'conditions': [
['OS == "mac"', {
'xcode_settings': {
'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ],
- 'OTHER_CFLAGS': [ '<@(cflags)' ],
}
}, {
'cflags_cc': [ '<@(cflags_cc)' ],
- 'cflags': [ '<@(cflags)' ],
}]
],
- 'direct_dependent_settings': {
- 'include_dirs': [
- '../include',
- ],
+
+ 'link_settings': {
'conditions': [
['OS == "mac"', {
- 'xcode_settings': {
- 'OTHER_LDFLAGS': [ '<@(ldflags)' ],
- }
+ 'libraries': [ '<@(libraries)' ],
+ 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)' ] }
}, {
- 'ldflags': [ '<@(ldflags)' ],
+ 'libraries': [ '<@(libraries)', '<@(ldflags)' ],
}]
],
},
+
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '../include',
+ ],
+ },
},
],
}
diff --git a/gyp/mbgl-osx.gypi b/gyp/platform-ios.gypi
index 98b66e62b7..bb1b450728 100644
--- a/gyp/mbgl-osx.gypi
+++ b/gyp/platform-ios.gypi
@@ -1,39 +1,55 @@
{
'targets': [
- { 'target_name': 'mbgl-osx',
- 'product_name': 'mbgl-osx',
+ { 'target_name': 'platform-ios',
+ 'product_name': 'mbgl-platform-ios',
'type': 'static_library',
'standalone_static_library': 1,
'hard_dependency': 1,
'dependencies': [
'version',
],
+
'sources': [
- '../platform/osx/cache_database_application_support.mm',
'../platform/darwin/log_nslog.mm',
'../platform/darwin/string_nsstring.mm',
- '../platform/darwin/http_request_baton_cocoa.mm',
'../platform/darwin/application_root.mm',
+ '../platform/darwin/asset_root.mm',
'../platform/darwin/image.mm',
- '../platform/default/asset_request_libuv.cpp',
],
+
+ 'variables': {
+ 'cflags_cc': [
+ '<@(uv_cflags)',
+ '<@(boost_cflags)',
+ ],
+ 'libraries': [
+ '<@(uv_static_libs)',
+ ],
+ 'ldflags': [
+ '-framework ImageIO',
+ '-framework MobileCoreServices',
+ ],
+ },
+
'include_dirs': [
'../include',
- '../src',
],
+
'xcode_settings': {
- 'OTHER_CPLUSPLUSFLAGS': [ '<@(uv_cflags)' ],
+ 'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ],
},
+
+ 'link_settings': {
+ 'libraries': [ '<@(libraries)' ],
+ 'xcode_settings': {
+ 'OTHER_LDFLAGS': [ '<@(ldflags)' ],
+ },
+ },
+
'direct_dependent_settings': {
'include_dirs': [
'../include',
],
- 'xcode_settings': {
- 'OTHER_LDFLAGS': [
- '-framework ImageIO',
- '-framework CoreServices',
- ],
- },
},
},
],
diff --git a/gyp/mbgl-linux.gypi b/gyp/platform-linux.gypi
index c515e68bed..30715f8289 100644
--- a/gyp/mbgl-linux.gypi
+++ b/gyp/platform-linux.gypi
@@ -1,82 +1,78 @@
{
'targets': [
- { 'target_name': 'mbgl-linux',
- 'product_name': 'mbgl-linux',
+ { 'target_name': 'platform-linux',
+ 'product_name': 'mbgl-platform-linux',
'type': 'static_library',
'standalone_static_library': 1,
'hard_dependency': 1,
'dependencies': [
'version',
],
+
+ 'sources': [
+ '../platform/default/log_stderr.cpp',
+ '../platform/default/string_stdlib.cpp',
+ '../platform/default/application_root.cpp',
+ '../platform/default/asset_root.cpp',
+ '../platform/default/image.cpp',
+ '../platform/default/image_reader.cpp',
+ '../platform/default/png_reader.cpp',
+ '../platform/default/jpeg_reader.cpp',
+ ],
+
'variables': {
'cflags_cc': [
'<@(png_cflags)',
'<@(jpeg_cflags)',
'<@(uv_cflags)',
- '<@(curl_cflags)',
- '<@(nu_cflags)',
- '-I<(boost_root)/include',
- ],
- 'cflags': [
- '<@(uv_cflags)',
'<@(nu_cflags)',
+ '<@(boost_cflags)',
],
'ldflags': [
'<@(png_ldflags)',
'<@(jpeg_ldflags)',
'<@(uv_ldflags)',
- '<@(curl_ldflags)',
'<@(nu_ldflags)',
],
- },
- 'sources': [
- '../platform/default/cache_database_tmp.cpp',
- '../platform/default/log_stderr.cpp',
- '../platform/default/string_stdlib.cpp',
- '../platform/default/asset_request_libuv.cpp',
- '../platform/default/http_request_baton_curl.cpp',
- '../platform/default/application_root.cpp',
- '../platform/default/image.cpp',
- '../platform/default/image_reader.cpp',
- '../platform/default/png_reader.cpp',
- '../platform/default/jpeg_reader.cpp',
- ],
- 'include_dirs': [
- '../include',
- '../src',
- ],
- 'link_settings': {
'libraries': [
'<@(png_static_libs)',
'<@(jpeg_static_libs)',
- '<@(nu_static_libs)'
+ '<@(uv_static_libs)',
+ '<@(nu_static_libs)',
],
},
+
+ 'include_dirs': [
+ '../include',
+ '../src',
+ ],
+
'conditions': [
['OS == "mac"', {
'xcode_settings': {
'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ],
- 'OTHER_CFLAGS': [ '<@(cflags)' ],
}
}, {
'cflags_cc': [ '<@(cflags_cc)' ],
- 'cflags': [ '<@(cflags)' ],
}]
],
- 'direct_dependent_settings': {
- 'include_dirs': [
- '../include',
- ],
+
+ 'link_settings': {
'conditions': [
['OS == "mac"', {
- 'xcode_settings': {
- 'OTHER_LDFLAGS': [ '<@(ldflags)' ],
- }
+ 'libraries': [ '<@(libraries)' ],
+ 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)' ] }
}, {
- 'ldflags': [ '<@(ldflags)' ],
+ 'libraries': [ '<@(libraries)', '<@(ldflags)' ],
}]
],
},
+
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '../include',
+ ],
+ },
},
],
}
diff --git a/gyp/platform-osx.gypi b/gyp/platform-osx.gypi
new file mode 100644
index 0000000000..e3fe7b0d53
--- /dev/null
+++ b/gyp/platform-osx.gypi
@@ -0,0 +1,58 @@
+{
+ 'targets': [
+ { 'target_name': 'platform-osx',
+ 'product_name': 'mbgl-platform-osx',
+ 'type': 'static_library',
+ 'standalone_static_library': 1,
+ 'hard_dependency': 1,
+ 'dependencies': [
+ 'version',
+ ],
+
+ 'sources': [
+ '../platform/darwin/log_nslog.mm',
+ '../platform/darwin/string_nsstring.mm',
+ '../platform/darwin/application_root.mm',
+ '../platform/darwin/asset_root.mm',
+ '../platform/darwin/image.mm',
+ ],
+
+ 'variables': {
+ 'cflags_cc': [
+ '<@(uv_cflags)',
+ '<@(boost_cflags)',
+ ],
+ 'libraries': [
+ '<@(uv_static_libs)',
+ ],
+ 'ldflags': [
+ '-framework Foundation',
+ '-framework ImageIO',
+ '-framework CoreServices',
+ ],
+ },
+
+ 'include_dirs': [
+ '../include',
+ '../src',
+ ],
+
+ 'xcode_settings': {
+ 'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ],
+ },
+
+ 'link_settings': {
+ 'libraries': [ '<@(libraries)' ],
+ 'xcode_settings': {
+ 'OTHER_LDFLAGS': [ '<@(ldflags)' ],
+ },
+ },
+
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '../include',
+ ],
+ },
+ },
+ ],
+}
diff --git a/gyp/standalone.gypi b/gyp/standalone.gypi
new file mode 100644
index 0000000000..7f222016cd
--- /dev/null
+++ b/gyp/standalone.gypi
@@ -0,0 +1,32 @@
+{
+ 'targets': [
+ { 'target_name': 'everything',
+ 'type': 'none',
+ 'hard_dependency': 1,
+
+ 'dependencies': [
+ 'core',
+ 'platform-<(platform_lib)',
+ 'http-<(http_lib)',
+ 'asset-<(asset_lib)',
+ 'cache-<(cache_lib)',
+ 'headless-<(headless_lib)',
+ ],
+ },
+
+ { 'target_name': 'standalone',
+ 'product_name': 'libmbgl.a',
+ 'type': 'executable',
+ 'hard_dependency': 1,
+
+ 'dependencies': [
+ 'core',
+ 'platform-<(platform_lib)',
+ 'http-<(http_lib)',
+ 'asset-<(asset_lib)',
+ 'cache-<(cache_lib)',
+ 'headless-<(headless_lib)',
+ ],
+ },
+ ],
+} \ No newline at end of file
diff --git a/gyp/version.gypi b/gyp/version.gypi
index 341d9d3a2b..68f777f0a4 100644
--- a/gyp/version.gypi
+++ b/gyp/version.gypi
@@ -13,7 +13,7 @@
'outputs': [
'<(SHARED_INTERMEDIATE_DIR)/include/mbgl/util/version.hpp',
],
- 'action': ['<@(python)', 'scripts/build-version.py', '<(SHARED_INTERMEDIATE_DIR)', '<!@(git describe --tags --always --abbrev=0)', '<!@(git rev-parse HEAD)'],
+ 'action': ['<@(python)', '<@(_inputs)', '<(SHARED_INTERMEDIATE_DIR)', '<!@(git describe --tags --always --abbrev=0)', '<!@(git rev-parse HEAD)'],
}
],
'direct_dependent_settings': {
diff --git a/include/mbgl/android/jni.hpp b/include/mbgl/android/jni.hpp
index 5f71f697d0..698c024731 100644
--- a/include/mbgl/android/jni.hpp
+++ b/include/mbgl/android/jni.hpp
@@ -2,7 +2,11 @@
#define MBGL_ANDROID_JNI
#include <string>
-#include <jni.h>
+
+// Forward definition of JNI types
+typedef class _jclass* jclass;
+typedef struct _jmethodID* jmethodID;
+typedef struct _jfieldID* jfieldID;
namespace mbgl {
namespace android {
diff --git a/include/mbgl/android/native_map_view.hpp b/include/mbgl/android/native_map_view.hpp
index c1bd085e9d..bfe544c2b0 100644
--- a/include/mbgl/android/native_map_view.hpp
+++ b/include/mbgl/android/native_map_view.hpp
@@ -4,7 +4,8 @@
#include <mbgl/map/map.hpp>
#include <mbgl/map/view.hpp>
#include <mbgl/util/noncopyable.hpp>
-#include <mbgl/storage/caching_http_file_source.hpp>
+#include <mbgl/storage/default/sqlite_cache.hpp>
+#include <mbgl/storage/default_file_source.hpp>
#include <string>
#include <jni.h>
@@ -28,7 +29,7 @@ public:
void notifyMapChange(mbgl::MapChange change, std::chrono::steady_clock::duration delay = std::chrono::steady_clock::duration::zero()) override;
mbgl::Map &getMap();
- mbgl::CachingHTTPFileSource &getFileSource();
+ mbgl::DefaultFileSource &getFileSource();
void initializeDisplay();
void terminateDisplay();
@@ -61,7 +62,8 @@ private:
ANativeWindow *window = nullptr;
- mbgl::CachingHTTPFileSource fileSource;
+ mbgl::SQLiteCache fileCache;
+ mbgl::DefaultFileSource fileSource;
mbgl::Map map;
EGLDisplay display = EGL_NO_DISPLAY;
diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp
index 62f0d62014..ae0228544e 100644
--- a/include/mbgl/map/map.hpp
+++ b/include/mbgl/map/map.hpp
@@ -123,6 +123,10 @@ public:
void startRotating();
void stopRotating();
+ // API
+ void setAccessToken(const std::string &token);
+ const std::string &getAccessToken() const;
+
// Debug
void setDebug(bool value);
void toggleDebug();
@@ -160,7 +164,10 @@ private:
Mode mode = Mode::None;
+public: // TODO: make private again
std::unique_ptr<uv::loop> loop;
+
+private:
std::unique_ptr<uv::worker> workers;
std::thread thread;
std::unique_ptr<uv::async> asyncTerminate;
@@ -191,7 +198,7 @@ private:
View &view;
-#ifndef NDEBUG
+#ifdef DEBUG
const std::thread::id mainThread;
std::thread::id mapThread;
#endif
@@ -214,6 +221,7 @@ private:
std::string styleURL;
std::string styleJSON = "";
std::vector<std::string> classes;
+ std::string accessToken;
std::chrono::steady_clock::duration defaultTransitionDuration;
diff --git a/include/mbgl/platform/default/headless_view.hpp b/include/mbgl/platform/default/headless_view.hpp
index e29f529f3d..2fad430e6e 100644
--- a/include/mbgl/platform/default/headless_view.hpp
+++ b/include/mbgl/platform/default/headless_view.hpp
@@ -7,6 +7,7 @@
#define GL_GLEXT_PROTOTYPES
#include <mbgl/platform/default/glx.h>
#define MBGL_USE_GLX 1
+#undef Status
#endif
#include <mbgl/map/view.hpp>
diff --git a/include/mbgl/platform/platform.hpp b/include/mbgl/platform/platform.hpp
index b4aaccb8bd..ea630c0956 100644
--- a/include/mbgl/platform/platform.hpp
+++ b/include/mbgl/platform/platform.hpp
@@ -17,10 +17,11 @@ std::string uppercase(const std::string &string);
// Lowercase a string, potentially using platform-specific routines.
std::string lowercase(const std::string &string);
-// Returns the path to the default cache database on this system.
-std::string defaultCacheDatabase();
+// Returns the path to the root folder of the application.
+const std::string &applicationRoot();
-std::string applicationRoot();
+// Returns the path to the asset location.
+const std::string &assetRoot();
// 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/storage/asset_request.hpp b/include/mbgl/storage/asset_request.hpp
deleted file mode 100644
index 3114d41ad2..0000000000
--- a/include/mbgl/storage/asset_request.hpp
+++ /dev/null
@@ -1,27 +0,0 @@
-#ifndef MBGL_STORAGE_ASSET_REQUEST
-#define MBGL_STORAGE_ASSET_REQUEST
-
-#include <mbgl/storage/base_request.hpp>
-
-namespace mbgl {
-
-typedef struct uv_loop_s uv_loop_t;
-
-struct AssetRequestBaton;
-
-class AssetRequest : public BaseRequest {
-public:
- AssetRequest(const std::string &path, uv_loop_t *loop);
- ~AssetRequest();
-
- void cancel();
-
-private:
- AssetRequestBaton *ptr = nullptr;
-
- friend struct AssetRequestBaton;
-};
-
-}
-
-#endif
diff --git a/include/mbgl/storage/asset_request_baton.hpp b/include/mbgl/storage/asset_request_baton.hpp
deleted file mode 100644
index 2cbb6c51c0..0000000000
--- a/include/mbgl/storage/asset_request_baton.hpp
+++ /dev/null
@@ -1,38 +0,0 @@
-#ifndef MBGL_STORAGE_ASSET_REQUEST_BATON
-#define MBGL_STORAGE_ASSET_REQUEST_BATON
-
-#include <mbgl/util/uv.hpp>
-#include <thread>
-
-#include <uv.h>
-
-namespace mbgl {
-
-class AssetRequest;
-
-struct AssetRequestBaton {
- AssetRequestBaton(AssetRequest *request_, const std::string &path, uv_loop_t *loop);
-
- const std::thread::id threadId;
- AssetRequest *request = nullptr;
- std::unique_ptr<uv::async> asyncRun;
- std::string path;
- bool canceled = false;
-
- void cancel();
- static void notifyError(AssetRequestBaton *ptr, const int code, const char *message);
- static void notifySuccess(AssetRequestBaton *ptr, const std::string body);
- static void cleanup(AssetRequestBaton *ptr);
-
- // IMPLEMENT THIS PLATFORM SPECIFIC FUNCTION:
-
- // Called to load the asset. Platform-specific implementation.
- static void run(AssetRequestBaton *ptr);
-
-};
-
-
-}
-
-
-#endif
diff --git a/include/mbgl/storage/caching_http_file_source.hpp b/include/mbgl/storage/caching_http_file_source.hpp
deleted file mode 100644
index 655afa6396..0000000000
--- a/include/mbgl/storage/caching_http_file_source.hpp
+++ /dev/null
@@ -1,62 +0,0 @@
-#ifndef MBGL_STORAGE_CACHING_HTTP_FILE_SOURCE
-#define MBGL_STORAGE_CACHING_HTTP_FILE_SOURCE
-
-#include <mbgl/storage/file_source.hpp>
-
-#include <thread>
-#include <unordered_map>
-
-typedef struct uv_messenger_s uv_messenger_t;
-
-namespace mbgl {
-
-class BaseRequest;
-class SQLiteStore;
-
-class CachingHTTPFileSource : public FileSource {
-public:
- CachingHTTPFileSource(const std::string &path_);
- virtual ~CachingHTTPFileSource();
-
- // Stores and checks the libuv loop for requests
- void setLoop(uv_loop_t*);
- bool hasLoop();
- void clearLoop();
-
- // Set the base path/URL for relative requests
- void setBase(std::string);
-
- // Set the Mapbox API access token
- void setAccessToken(std::string);
-
- // Get the Mapbox API access token
- std::string getAccessToken() const;
-
- std::unique_ptr<Request> request(ResourceType type, const std::string &url);
-
- void prepare(std::function<void()> fn);
-
- // Call this when the network status reachability changed.
- void setReachability(bool reachable);
-
-private:
- std::thread::id threadId;
-
- // Mapbox API access token.
- std::string accessToken;
-
- // Path to the cache database.
- std::string path;
-
- // 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;
- util::ptr<SQLiteStore> store;
- uv_loop_t *loop = nullptr;
- uv_messenger_t *queue = nullptr;
-};
-
-}
-
-#endif
diff --git a/include/mbgl/storage/default/asset_request.hpp b/include/mbgl/storage/default/asset_request.hpp
new file mode 100644
index 0000000000..c582c025fb
--- /dev/null
+++ b/include/mbgl/storage/default/asset_request.hpp
@@ -0,0 +1,24 @@
+#ifndef MBGL_STORAGE_DEFAULT_ASSET_REQUEST
+#define MBGL_STORAGE_DEFAULT_ASSET_REQUEST
+
+#include "shared_request_base.hpp"
+
+namespace mbgl {
+
+class AssetRequest : public SharedRequestBase {
+public:
+ AssetRequest(DefaultFileSource *source, const Resource &resource);
+
+ void start(uv_loop_t *loop, std::unique_ptr<Response> response = nullptr);
+ void cancel();
+
+private:
+ ~AssetRequest();
+ void *ptr = nullptr;
+
+ friend class AssetRequestImpl;
+};
+
+}
+
+#endif
diff --git a/include/mbgl/storage/default/http_context.hpp b/include/mbgl/storage/default/http_context.hpp
new file mode 100644
index 0000000000..6b9518dab3
--- /dev/null
+++ b/include/mbgl/storage/default/http_context.hpp
@@ -0,0 +1,76 @@
+#ifndef MBGL_STORAGE_DEFAULT_HTTP_CONTEXT
+#define MBGL_STORAGE_DEFAULT_HTTP_CONTEXT
+
+#include "thread_context.hpp"
+#include <mbgl/storage/network_status.hpp>
+
+#include <set>
+
+namespace mbgl {
+
+class HTTPRequest;
+
+// This is a template class that provides a per-thread Context object. It can be used by HTTP
+// implementations to store global state. It also implements the NetworkStatus mechanism and
+// triggers immediate retries on all requests waiting for network status changes.
+
+template <typename Context>
+class HTTPContext : public ThreadContext<Context> {
+public:
+ HTTPContext(uv_loop_t *loop);
+ ~HTTPContext();
+
+ void addRequest(HTTPRequest *request);
+ void removeRequest(HTTPRequest *baton);
+
+public:
+ // Will be fired when the network status becomes reachable.
+ uv_async_t *reachability = nullptr;
+
+ // A list of all pending HTTPRequestImpls that we need to notify when the network status
+ // changes.
+ std::set<HTTPRequest *> requests;
+};
+
+template <typename Context>
+HTTPContext<Context>::HTTPContext(uv_loop_t *loop_)
+ : ThreadContext<Context>(loop_) {
+ reachability = new uv_async_t;
+ reachability->data = this;
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+ uv_async_init(loop_, reachability, [](uv_async_t *async, int) {
+#else
+ uv_async_init(loop_, reachability, [](uv_async_t *async) {
+#endif
+ for (auto request : reinterpret_cast<Context *>(async->data)->requests) {
+ request->retryImmediately();
+ }
+ });
+ // Allow the loop to quit even though this handle is still active.
+ uv_unref(reinterpret_cast<uv_handle_t *>(reachability));
+ NetworkStatus::Subscribe(reachability);
+}
+
+template <typename Context>
+HTTPContext<Context>::~HTTPContext() {
+ MBGL_VERIFY_THREAD(HTTPContext<Context>::tid);
+
+ assert(requests.empty());
+
+ NetworkStatus::Unsubscribe(reachability);
+ uv::close(reachability);
+}
+
+template <typename Context>
+void HTTPContext<Context>::addRequest(HTTPRequest *request) {
+ requests.insert(request);
+}
+
+template <typename Context>
+void HTTPContext<Context>::removeRequest(HTTPRequest *request) {
+ requests.erase(request);
+}
+
+}
+
+#endif
diff --git a/include/mbgl/storage/default/http_request.hpp b/include/mbgl/storage/default/http_request.hpp
new file mode 100644
index 0000000000..914e622f13
--- /dev/null
+++ b/include/mbgl/storage/default/http_request.hpp
@@ -0,0 +1,26 @@
+#ifndef MBGL_STORAGE_DEFAULT_HTTP_REQUEST
+#define MBGL_STORAGE_DEFAULT_HTTP_REQUEST
+
+#include "shared_request_base.hpp"
+
+namespace mbgl {
+
+class HTTPRequest : public SharedRequestBase {
+public:
+ HTTPRequest(DefaultFileSource *source, const Resource &resource);
+
+ void start(uv_loop_t *loop, std::unique_ptr<Response> response = nullptr);
+ void cancel();
+
+ void retryImmediately();
+
+private:
+ ~HTTPRequest();
+ void *ptr = nullptr;
+
+ friend class HTTPRequestImpl;
+};
+
+}
+
+#endif
diff --git a/include/mbgl/storage/default/request.hpp b/include/mbgl/storage/default/request.hpp
new file mode 100644
index 0000000000..81ed14a568
--- /dev/null
+++ b/include/mbgl/storage/default/request.hpp
@@ -0,0 +1,51 @@
+#ifndef MBGL_STORAGE_DEFAULT_REQUEST
+#define MBGL_STORAGE_DEFAULT_REQUEST
+
+#include <mbgl/storage/resource.hpp>
+
+#include <mbgl/util/util.hpp>
+#include <mbgl/util/noncopyable.hpp>
+
+#include <functional>
+#include <memory>
+
+typedef struct uv_async_s uv_async_t;
+typedef struct uv_loop_s uv_loop_t;
+
+namespace mbgl {
+
+class Response;
+
+class Request : private util::noncopyable {
+ MBGL_STORE_THREAD(tid)
+
+public:
+ using Callback = std::function<void(const Response &)>;
+ Request(const Resource &resource, uv_loop_t *loop, Callback callback);
+ ~Request();
+
+public:
+ // May be called from any thread.
+ void notify(const std::shared_ptr<const Response> &response);
+ void destruct();
+
+ // May be called only from the thread the Request was created in.
+ void cancel();
+
+private:
+ static void notifyCallback(uv_async_t *async);
+ static void cancelCallback(uv_async_t *async);
+
+private:
+ uv_async_t *notify_async = nullptr;
+ uv_async_t *destruct_async = nullptr;
+ Callback callback;
+ std::shared_ptr<const Response> response;
+
+public:
+ const Resource resource;
+};
+
+}
+
+#endif
diff --git a/include/mbgl/storage/default/shared_request_base.hpp b/include/mbgl/storage/default/shared_request_base.hpp
new file mode 100644
index 0000000000..a0f3e8d4e3
--- /dev/null
+++ b/include/mbgl/storage/default/shared_request_base.hpp
@@ -0,0 +1,88 @@
+#ifndef MBGL_STORAGE_DEFAULT_SHARED_REQUEST_BASE
+#define MBGL_STORAGE_DEFAULT_SHARED_REQUEST_BASE
+
+#include <mbgl/storage/resource.hpp>
+#include <mbgl/storage/file_cache.hpp>
+#include <mbgl/storage/default_file_source.hpp>
+#include <mbgl/util/util.hpp>
+#include <mbgl/util/noncopyable.hpp>
+
+#include <string>
+#include <set>
+#include <cassert>
+
+typedef struct uv_loop_s uv_loop_t;
+
+namespace mbgl {
+
+class Request;
+class Response;
+class DefaultFileSource;
+
+class SharedRequestBase : private util::noncopyable {
+protected:
+ MBGL_STORE_THREAD(tid)
+
+public:
+ SharedRequestBase(DefaultFileSource *source_, const Resource &resource_)
+ : resource(resource_), source(source_) {}
+
+ virtual void start(uv_loop_t *loop, std::unique_ptr<Response> response = nullptr) = 0;
+ virtual void cancel() = 0;
+
+ void notify(std::unique_ptr<Response> response, FileCache::Hint hint) {
+ MBGL_VERIFY_THREAD(tid);
+
+ if (source) {
+ source->notify(this, observers, std::shared_ptr<const Response>(response.release()),
+ hint);
+ }
+ }
+
+ void subscribe(Request *request) {
+ MBGL_VERIFY_THREAD(tid);
+
+ observers.insert(request);
+ }
+
+ void unsubscribeAll() {
+ MBGL_VERIFY_THREAD(tid);
+
+ source = nullptr;
+ observers.clear();
+ cancel();
+ }
+
+ void unsubscribe(Request *request) {
+ MBGL_VERIFY_THREAD(tid);
+
+ observers.erase(request);
+
+ if (observers.empty()) {
+ // There are no observers anymore. We are initiating cancelation.
+ if (source) {
+ // First, remove this SharedRequestBase from the source.
+ source->notify(this, observers, nullptr, FileCache::Hint::No);
+ }
+
+ // Then, initiate cancelation of this request
+ cancel();
+ }
+ }
+
+protected:
+ virtual ~SharedRequestBase() {
+ MBGL_VERIFY_THREAD(tid);
+ }
+
+public:
+ const Resource resource;
+
+private:
+ DefaultFileSource *source = nullptr;
+ std::set<Request *> observers;
+};
+
+}
+
+#endif
diff --git a/include/mbgl/storage/default/sqlite_cache.hpp b/include/mbgl/storage/default/sqlite_cache.hpp
new file mode 100644
index 0000000000..8f2746561c
--- /dev/null
+++ b/include/mbgl/storage/default/sqlite_cache.hpp
@@ -0,0 +1,52 @@
+#ifndef MBGL_STORAGE_DEFAULT_SQLITE_CACHE
+#define MBGL_STORAGE_DEFAULT_SQLITE_CACHE
+
+#include <mbgl/storage/file_cache.hpp>
+
+#include <string>
+#include <thread>
+
+typedef struct uv_loop_s uv_loop_t;
+
+namespace mapbox { namespace util { template<typename... Types> class variant; } }
+namespace mapbox { namespace sqlite { class Database; class Statement; } }
+
+namespace mbgl {
+
+namespace util { template <typename T> class AsyncQueue; }
+
+class SQLiteCache : public FileCache {
+ struct GetAction;
+ struct PutAction;
+ struct RefreshAction;
+ struct StopAction;
+ using Action = mapbox::util::variant<GetAction, PutAction, RefreshAction, StopAction>;
+ using Queue = util::AsyncQueue<Action>;
+
+public:
+ SQLiteCache(const std::string &path = ":memory:");
+ ~SQLiteCache();
+
+ void get(const Resource &resource, std::function<void(std::unique_ptr<Response>)> callback);
+ void put(const Resource &resource, std::shared_ptr<const Response> response, Hint hint);
+
+private:
+ struct ActionDispatcher;
+ void process(GetAction &action);
+ void process(PutAction &action);
+ void process(RefreshAction &action);
+ void process(StopAction &action);
+
+ void createDatabase();
+
+ const std::string path;
+ uv_loop_t *loop = nullptr;
+ Queue *queue = nullptr;
+ std::thread thread;
+ std::unique_ptr<::mapbox::sqlite::Database> db;
+ std::unique_ptr<::mapbox::sqlite::Statement> getStmt, putStmt, refreshStmt;
+};
+
+}
+
+#endif
diff --git a/include/mbgl/storage/default/thread_context.hpp b/include/mbgl/storage/default/thread_context.hpp
new file mode 100644
index 0000000000..763c83a25b
--- /dev/null
+++ b/include/mbgl/storage/default/thread_context.hpp
@@ -0,0 +1,78 @@
+#ifndef MBGL_STORAGE_DEFAULT_THREAD_CONTEXT
+#define MBGL_STORAGE_DEFAULT_THREAD_CONTEXT
+
+#include <mbgl/util/noncopyable.hpp>
+#include <mbgl/util/std.hpp>
+#include <mbgl/util/util.hpp>
+#include <mbgl/util/uv.hpp>
+
+#include <uv.h>
+#include <pthread.h>
+
+#include <map>
+#include <cassert>
+
+namespace mbgl {
+
+// This is a template class that provides a per-thread and per-loop Context object. It can be used
+// by implementations to store global state.
+
+template <typename Context>
+class ThreadContext : private util::noncopyable {
+protected:
+ MBGL_STORE_THREAD(tid)
+ using Map = std::map<uv_loop_t *, std::unique_ptr<Context>>;
+
+public:
+ static Context *Get(uv_loop_t *loop);
+
+private:
+ static pthread_key_t key;
+ static pthread_once_t once;
+
+public:
+ ThreadContext(uv_loop_t *loop);
+ ~ThreadContext();
+
+public:
+ uv_loop_t *loop;
+};
+
+template <typename Context>
+Context *ThreadContext<Context>::Get(uv_loop_t *loop) {
+ pthread_once(&once, []() {
+ pthread_key_create(&key, [](void *ptr) {
+ assert(ptr);
+ delete reinterpret_cast<Map *>(ptr);
+ });
+ });
+ auto contexts = reinterpret_cast<Map *>(pthread_getspecific(key));
+ if (!contexts) {
+ contexts = new Map();
+ pthread_setspecific(key, contexts);
+ }
+
+ // Now find a ThreadContext that matches the requested loop.
+ auto it = contexts->find(loop);
+ if (it == contexts->end()) {
+ auto result = contexts->emplace(loop, util::make_unique<Context>(loop));
+ assert(result.second); // Make sure it was actually inserted.
+ return result.first->second.get();
+ } else {
+ return it->second.get();
+ }
+}
+
+template <typename Context>
+ThreadContext<Context>::ThreadContext(uv_loop_t *loop_) : loop(loop_) {
+}
+
+template <typename Context>
+ThreadContext<Context>::~ThreadContext() {
+ MBGL_VERIFY_THREAD(tid);
+}
+
+
+}
+
+#endif
diff --git a/include/mbgl/storage/default_file_source.hpp b/include/mbgl/storage/default_file_source.hpp
new file mode 100644
index 0000000000..21048e99e5
--- /dev/null
+++ b/include/mbgl/storage/default_file_source.hpp
@@ -0,0 +1,60 @@
+#ifndef MBGL_STORAGE_DEFAULT_DEFAULT_FILE_SOURCE
+#define MBGL_STORAGE_DEFAULT_DEFAULT_FILE_SOURCE
+
+#include <mbgl/storage/file_source.hpp>
+#include <mbgl/storage/file_cache.hpp>
+
+#include <set>
+#include <unordered_map>
+#include <thread>
+
+namespace mapbox { namespace util { template<typename... Types> class variant; } }
+
+namespace mbgl {
+
+namespace util { template <typename T> class AsyncQueue; }
+
+class SharedRequestBase;
+
+class DefaultFileSource : public FileSource {
+public:
+ DefaultFileSource(FileCache *cache);
+ DefaultFileSource(FileCache *cache, uv_loop_t *loop);
+ ~DefaultFileSource();
+
+ Request *request(const Resource &resource, uv_loop_t *loop, Callback callback);
+ void cancel(Request *request);
+ void request(const Resource &resource, Callback callback);
+
+ enum class CacheHint : uint8_t { Full, Refresh, No };
+ void notify(SharedRequestBase *sharedRequest, const std::set<Request *> &observers,
+ std::shared_ptr<const Response> response, FileCache::Hint hint);
+
+private:
+ struct ActionDispatcher;
+ struct AddRequestAction;
+ struct RemoveRequestAction;
+ struct ResultAction;
+ struct StopAction;
+ using Action =
+ mapbox::util::variant<AddRequestAction, RemoveRequestAction, ResultAction, StopAction>;
+ using Queue = util::AsyncQueue<Action>;
+
+ void process(AddRequestAction &action);
+ void process(RemoveRequestAction &action);
+ void process(ResultAction &action);
+ void process(StopAction &action);
+
+ SharedRequestBase *find(const Resource &resource);
+
+ std::unordered_map<Resource, SharedRequestBase *, Resource::Hash> pending;
+
+ uv_loop_t *loop = nullptr;
+ FileCache *cache = nullptr;
+ Queue *queue = nullptr;
+ std::thread thread;
+};
+
+}
+
+#endif
diff --git a/include/mbgl/storage/file_cache.hpp b/include/mbgl/storage/file_cache.hpp
new file mode 100644
index 0000000000..97e75a5d85
--- /dev/null
+++ b/include/mbgl/storage/file_cache.hpp
@@ -0,0 +1,27 @@
+#ifndef MBGL_STORAGE_FILE_CACHE
+#define MBGL_STORAGE_FILE_CACHE
+
+#include <mbgl/util/noncopyable.hpp>
+
+#include <functional>
+#include <memory>
+
+namespace mbgl {
+
+struct Resource;
+class Response;
+
+class FileCache : private util::noncopyable {
+public:
+ virtual ~FileCache() = default;
+
+ enum class Hint : uint8_t { Full, Refresh, No };
+
+ virtual void get(const Resource &resource,
+ std::function<void(std::unique_ptr<Response>)> callback) = 0;
+ virtual void put(const Resource &resource, std::shared_ptr<const Response> response, Hint hint) = 0;
+};
+
+}
+
+#endif
diff --git a/include/mbgl/storage/file_source.hpp b/include/mbgl/storage/file_source.hpp
index 23a1462ae8..8517d6e4a6 100644
--- a/include/mbgl/storage/file_source.hpp
+++ b/include/mbgl/storage/file_source.hpp
@@ -1,29 +1,38 @@
#ifndef MBGL_STORAGE_FILE_SOURCE
#define MBGL_STORAGE_FILE_SOURCE
+#include "response.hpp"
+#include "resource.hpp"
+
#include <mbgl/util/noncopyable.hpp>
-#include <mbgl/storage/resource_type.hpp>
-#include <mbgl/storage/request.hpp>
+#include <mbgl/util/std.hpp>
+#include <mbgl/util/util.hpp>
-#include <string>
#include <functional>
typedef struct uv_loop_s uv_loop_t;
-
namespace mbgl {
-class FileSource : public util::noncopyable {
+class Request;
+
+class FileSource : private util::noncopyable {
+protected:
+ MBGL_STORE_THREAD(tid)
+
public:
virtual ~FileSource() = default;
- virtual void setLoop(uv_loop_t*) = 0;
- virtual bool hasLoop() = 0;
- virtual void clearLoop() = 0;
+ using Callback = std::function<void(const Response &)>;
+
+ // These can be called from any thread. The callback will be invoked in the loop.
+ // You can only cancel a request from the same thread it was created in.
+ virtual Request *request(const Resource &resource, uv_loop_t *loop, Callback callback) = 0;
+ virtual void cancel(Request *request) = 0;
- virtual void setBase(std::string) = 0;
- virtual std::unique_ptr<Request> request(ResourceType type, const std::string &url) = 0;
- virtual void prepare(std::function<void()> fn) = 0;
+ // These can be called from any thread. The callback will be invoked in an arbitrary other thread.
+ // You cannot cancel these requests.
+ virtual void request(const Resource &resource, Callback callback) = 0;
};
}
diff --git a/include/mbgl/storage/http_request_baton.hpp b/include/mbgl/storage/http_request_baton.hpp
deleted file mode 100644
index 11abfb71d4..0000000000
--- a/include/mbgl/storage/http_request_baton.hpp
+++ /dev/null
@@ -1,74 +0,0 @@
-#ifndef MBGL_STORAGE_HTTP_REQUEST_BATON
-#define MBGL_STORAGE_HTTP_REQUEST_BATON
-
-#include <mbgl/storage/response.hpp>
-#include <mbgl/util/ptr.hpp>
-
-#include <string>
-#include <thread>
-
-typedef struct uv_async_s uv_async_t;
-
-namespace mbgl {
-
-class HTTPRequest;
-
-enum class HTTPResponseType : int8_t {
- // This error probably won't be resolved by retrying anytime soon. We are giving up.
- PermanentError = -5,
-
- // This error might be resolved by waiting some time (e.g. server issues).
- // We are going to do an exponential back-off and will try again in a few seconds.
- TemporaryError = -4,
-
- // This error was caused by a temporary error and it is likely that it will be resolved
- // immediately. We are going to try again right away. This is like the TemporaryError, except
- // that we will not perform exponential back-off.
- SingularError = -3,
-
- // This error might be resolved once the network reachability status changes.
- // We are going to watch the network status for changes and will retry as soon as the operating
- // system notifies us of a network status change.
- ConnectionError = -2,
-
- // The request was canceled programatically.
- Canceled = -1,
-
- // The request is still in progress.
- Unknown = 0,
-
- // The request returned data successfully. We retrieved and decoded the data successfully.
- Successful = 1,
-
- // The request confirmed that the data wasn't changed. We already have the data.
- NotModified = 2,
-};
-
-struct HTTPRequestBaton {
- HTTPRequestBaton(const std::string &path);
- ~HTTPRequestBaton();
-
- const std::thread::id threadId;
- const std::string path;
-
- HTTPRequest *request = nullptr;
- uv_async_t *async = nullptr;
-
- HTTPResponseType type = HTTPResponseType::Unknown;
- std::unique_ptr<Response> response;
-
- // Implementation specific use.
- void *ptr = nullptr;
-
- // IMPLEMENT THESE 3 PLATFORM SPECIFIC FUNCTIONS:
-
- // Begin the HTTP request. Platform-specific implementation.
- static void start(const util::ptr<HTTPRequestBaton> &ptr);
-
- // This will be called to stop/cancel the HTTP request (if possible). Platform-specific implementation.
- static void stop(const util::ptr<HTTPRequestBaton> &ptr);
-};
-
-}
-
-#endif
diff --git a/include/mbgl/storage/network_status.hpp b/include/mbgl/storage/network_status.hpp
new file mode 100644
index 0000000000..cac2ae193b
--- /dev/null
+++ b/include/mbgl/storage/network_status.hpp
@@ -0,0 +1,25 @@
+#ifndef MBGL_STORAGE_NETWORK_STATUS
+#define MBGL_STORAGE_NETWORK_STATUS
+
+#include <mutex>
+#include <set>
+
+typedef struct uv_async_s uv_async_t;
+
+namespace mbgl {
+
+class NetworkStatus {
+public:
+ static void Reachable();
+
+ static void Subscribe(uv_async_t *async);
+ static void Unsubscribe(uv_async_t *async);
+
+private:
+ static std::mutex mtx;
+ static std::set<uv_async_t *> observers;
+};
+
+}
+
+#endif \ No newline at end of file
diff --git a/include/mbgl/storage/request.hpp b/include/mbgl/storage/request.hpp
deleted file mode 100644
index 845c9a6dad..0000000000
--- a/include/mbgl/storage/request.hpp
+++ /dev/null
@@ -1,41 +0,0 @@
-#ifndef MBGL_STORAGE_REQUEST
-#define MBGL_STORAGE_REQUEST
-
-#include <mbgl/storage/request_callback.hpp>
-#include <mbgl/storage/response.hpp>
-
-#include <mbgl/util/ptr.hpp>
-
-#include <thread>
-#include <forward_list>
-
-typedef struct uv_loop_s uv_loop_t;
-
-namespace mbgl {
-
-class BaseRequest;
-
-class Request {
-private:
- Request(const Request &) = delete;
- Request(Request &&) = delete;
- Request& operator=(const Request &) = delete;
- Request& operator=(Request &&) = delete;
-
-public:
- Request(const util::ptr<BaseRequest> &base);
- ~Request();
-
- void onload(CompletedCallback cb);
- void oncancel(AbortedCallback cb);
- void cancel();
-
-private:
- const std::thread::id thread_id;
- util::ptr<BaseRequest> base;
- std::forward_list<Callback *> callbacks;
-};
-
-}
-
-#endif \ No newline at end of file
diff --git a/include/mbgl/storage/request_callback.hpp b/include/mbgl/storage/request_callback.hpp
deleted file mode 100644
index 01427bd96d..0000000000
--- a/include/mbgl/storage/request_callback.hpp
+++ /dev/null
@@ -1,22 +0,0 @@
-#ifndef MBGL_STORAGE_REQUEST_CALLBACK
-#define MBGL_STORAGE_REQUEST_CALLBACK
-
-#include <mbgl/util/variant.hpp>
-
-#include <functional>
-
-namespace mbgl {
-
-class Response;
-
-using CompletedCallback = std::function<void(const Response &)>;
-using AbortedCallback = std::function<void()>;
-
-using Callback = mapbox::util::variant<
- CompletedCallback,
- AbortedCallback
->;
-
-}
-
-#endif
diff --git a/include/mbgl/storage/resource.hpp b/include/mbgl/storage/resource.hpp
new file mode 100644
index 0000000000..cfd52caa75
--- /dev/null
+++ b/include/mbgl/storage/resource.hpp
@@ -0,0 +1,35 @@
+#ifndef MBGL_STORAGE_RESOURCE
+#define MBGL_STORAGE_RESOURCE
+
+#include <string>
+#include <functional>
+
+namespace mbgl {
+
+struct Resource {
+ enum Kind : uint8_t {
+ Unknown = 0,
+ Tile = 1,
+ Glyphs = 2,
+ Image = 3,
+ JSON = 4,
+ };
+
+ const Kind kind;
+ const std::string url;
+
+ inline bool operator==(const Resource &res) const {
+ return kind == res.kind && url == res.url;
+ }
+
+ struct Hash {
+ std::size_t operator()(Resource const& r) const {
+ return std::hash<std::string>()(r.url) ^ (std::hash<uint8_t>()(r.kind) << 1);
+ }
+ };
+
+};
+
+}
+
+#endif
diff --git a/include/mbgl/storage/resource_type.hpp b/include/mbgl/storage/resource_type.hpp
deleted file mode 100644
index b7204a9fa1..0000000000
--- a/include/mbgl/storage/resource_type.hpp
+++ /dev/null
@@ -1,18 +0,0 @@
-#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
index 9357ad3c63..cf22d9002b 100644
--- a/include/mbgl/storage/response.hpp
+++ b/include/mbgl/storage/response.hpp
@@ -2,25 +2,21 @@
#define MBGL_STORAGE_RESPONSE
#include <string>
-#include <ctime>
namespace mbgl {
-
-
class Response {
public:
- long code = 0;
+ enum Status : bool { Error, Successful };
+
+ Status status = Error;
+ std::string message;
int64_t modified = 0;
int64_t expires = 0;
std::string etag;
std::string data;
-
- std::string message;
-
- static int64_t parseCacheControl(const char *value);
};
}
-#endif \ No newline at end of file
+#endif
diff --git a/include/mbgl/util/async_queue.hpp b/include/mbgl/util/async_queue.hpp
new file mode 100644
index 0000000000..b3eaabc319
--- /dev/null
+++ b/include/mbgl/util/async_queue.hpp
@@ -0,0 +1,95 @@
+#ifndef MBGL_UTIL_ASYNC_QUEUE
+#define MBGL_UTIL_ASYNC_QUEUE
+
+#include "std.hpp"
+
+#include <uv.h>
+
+#include <thread>
+#include <mutex>
+#include <functional>
+#include <queue>
+#include <string>
+
+
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+#define UV_ASYNC_PARAMS(handle) uv_async_t *handle, int
+#else
+#define UV_ASYNC_PARAMS(handle) uv_async_t *handle
+#endif
+
+namespace mbgl {
+namespace util {
+
+template <typename T>
+class AsyncQueue {
+public:
+ AsyncQueue(uv_loop_t *loop, std::function<void(T &)> fn) :
+ callback(fn) {
+ async.data = this;
+ uv_async_init(loop, &async, [](UV_ASYNC_PARAMS(handle)) {
+ auto q = reinterpret_cast<AsyncQueue *>(handle->data);
+ q->process();
+ });
+ }
+
+ void send(T &&data) {
+ {
+ std::lock_guard<std::mutex> lock(mutex);
+ queue.push(util::make_unique<T>(std::move(data)));
+ }
+ uv_async_send(&async);
+ }
+
+ void send(std::unique_ptr<T> data) {
+ {
+ std::lock_guard<std::mutex> lock(mutex);
+ queue.push(std::move(data));
+ }
+ uv_async_send(&async);
+ }
+
+ void stop() {
+ uv_close((uv_handle_t *)&async, [](uv_handle_t *handle) {
+ delete reinterpret_cast<AsyncQueue *>(handle->data);
+ });
+ }
+
+ void ref() {
+ uv_ref((uv_handle_t *)&async);
+ }
+
+ void unref() {
+ uv_unref((uv_handle_t *)&async);
+ }
+
+private:
+ ~AsyncQueue() {
+ }
+
+ void process() {
+ std::unique_ptr<T> item;
+ while (true) {
+ mutex.lock();
+ if (queue.empty()) {
+ mutex.unlock();
+ break;
+ }
+ item = std::move(queue.front());
+ queue.pop();
+ mutex.unlock();
+ callback(*item);
+ }
+ }
+
+private:
+ std::mutex mutex;
+ uv_async_t async;
+ std::queue<std::unique_ptr<T>> queue;
+ std::function<void(T &)> callback;
+};
+
+}
+}
+
+#endif
diff --git a/include/mbgl/util/util.hpp b/include/mbgl/util/util.hpp
new file mode 100644
index 0000000000..2b0fd9cdd5
--- /dev/null
+++ b/include/mbgl/util/util.hpp
@@ -0,0 +1,17 @@
+#ifndef MBGL_UTIL_UTIL
+#define MBGL_UTIL_UTIL
+
+#ifdef DEBUG
+
+#include <thread>
+#define MBGL_STORE_THREAD(tid) const std::thread::id tid = std::this_thread::get_id();
+#define MBGL_VERIFY_THREAD(tid) assert(tid == std::this_thread::get_id());
+
+#else
+
+#define MBGL_STORE_THREAD(tid)
+#define MBGL_VERIFY_THREAD(tid)
+
+#endif
+
+#endif
diff --git a/include/mbgl/util/uv.hpp b/include/mbgl/util/uv.hpp
index f59037c1d8..85f93e78bd 100644
--- a/include/mbgl/util/uv.hpp
+++ b/include/mbgl/util/uv.hpp
@@ -3,10 +3,11 @@
#include <string>
+typedef struct uv_handle_s uv_handle_t;
typedef struct uv_async_s uv_async_t;
typedef struct uv_timer_s uv_timer_t;
-typedef struct uv_handle_s uv_handle_t;
typedef struct uv_loop_s uv_loop_t;
+typedef struct uv_fs_s uv_fs_t;
namespace uv {
@@ -19,6 +20,15 @@ class worker;
class mutex;
class cond;
+const char *getFileRequestError(uv_fs_t *req);
+
+template <typename T>
+void close(T *specific) {
+ uv_close(reinterpret_cast<uv_handle_t *>(specific), [](uv_handle_t *generic) {
+ delete reinterpret_cast<T *>(generic);
+ });
+}
+
}
#endif
diff --git a/include/mbgl/util/variant.hpp b/include/mbgl/util/variant.hpp
index 2de195cd69..411f1918d5 100644
--- a/include/mbgl/util/variant.hpp
+++ b/include/mbgl/util/variant.hpp
@@ -10,7 +10,7 @@
#include <iosfwd>
#include <string>
-#include <mbgl/util/recursive_wrapper.hpp>
+#include "recursive_wrapper.hpp"
#ifdef _MSC_VER
// http://msdn.microsoft.com/en-us/library/z8y1yy88.aspx
@@ -34,7 +34,19 @@
// translates to 100
#define VARIANT_VERSION (VARIANT_MAJOR_VERSION*100000) + (VARIANT_MINOR_VERSION*100) + (VARIANT_PATCH_VERSION)
-namespace mapbox { namespace util { namespace detail {
+namespace mapbox { namespace util {
+
+// static visitor
+template <typename R = void>
+struct static_visitor
+{
+ using result_type = R;
+protected:
+ static_visitor() {}
+ ~static_visitor() {}
+};
+
+namespace detail {
static constexpr std::size_t invalid_value = std::size_t(-1);
@@ -109,18 +121,38 @@ struct select_type<0, T, Types...>
using type = T;
};
-} // namespace detail
-// static visitor
-template <typename R = void>
-struct static_visitor
+template <typename T, typename R = void>
+struct enable_if_type { using type = R; };
+
+template <typename F, typename V, typename Enable = void>
+struct result_of_unary_visit
{
- using result_type = R;
-protected:
- static_visitor() {}
- ~static_visitor() {}
+ using type = typename std::result_of<F(V&)>::type;
};
+template <typename F, typename V>
+struct result_of_unary_visit<F, V, typename enable_if_type<typename F::result_type>::type >
+{
+ using type = typename F::result_type;
+};
+
+template <typename F, typename V, class Enable = void>
+struct result_of_binary_visit
+{
+ using type = typename std::result_of<F(V&,V&)>::type;
+};
+
+
+template <typename F, typename V>
+struct result_of_binary_visit<F, V, typename enable_if_type<typename F::result_type>::type >
+{
+ using type = typename F::result_type;
+};
+
+
+} // namespace detail
+
template <std::size_t arg1, std::size_t ... others>
struct static_max;
@@ -225,7 +257,7 @@ struct dispatcher;
template <typename F, typename V, typename T, typename...Types>
struct dispatcher<F, V, T, Types...>
{
- using result_type = typename F::result_type;
+ using result_type = typename detail::result_of_unary_visit<F, V>::type;
VARIANT_INLINE static result_type apply_const(V const& v, F f)
{
if (v.get_type_index() == sizeof...(Types))
@@ -254,7 +286,7 @@ struct dispatcher<F, V, T, Types...>
template<typename F, typename V>
struct dispatcher<F, V>
{
- using result_type = typename F::result_type;
+ using result_type = typename detail::result_of_unary_visit<F, V>::type;
VARIANT_INLINE static result_type apply_const(V const&, F)
{
throw std::runtime_error(std::string("unary dispatch: FAIL ") + typeid(V).name());
@@ -273,7 +305,7 @@ struct binary_dispatcher_rhs;
template <typename F, typename V, typename T0, typename T1, typename...Types>
struct binary_dispatcher_rhs<F, V, T0, T1, Types...>
{
- using result_type = typename F::result_type;
+ using result_type = typename detail::result_of_binary_visit<F, V>::type;
VARIANT_INLINE static result_type apply_const(V const& lhs, V const& rhs, F f)
{
if (rhs.get_type_index() == sizeof...(Types)) // call binary functor
@@ -305,7 +337,7 @@ struct binary_dispatcher_rhs<F, V, T0, T1, Types...>
template<typename F, typename V, typename T>
struct binary_dispatcher_rhs<F, V, T>
{
- using result_type = typename F::result_type;
+ using result_type = typename detail::result_of_binary_visit<F, V>::type;
VARIANT_INLINE static result_type apply_const(V const&, V const&, F)
{
throw std::runtime_error("binary dispatch: FAIL");
@@ -323,7 +355,7 @@ struct binary_dispatcher_lhs;
template <typename F, typename V, typename T0, typename T1, typename...Types>
struct binary_dispatcher_lhs<F, V, T0, T1, Types...>
{
- using result_type = typename F::result_type;
+ using result_type = typename detail::result_of_binary_visit<F, V>::type;
VARIANT_INLINE static result_type apply_const(V const& lhs, V const& rhs, F f)
{
if (lhs.get_type_index() == sizeof...(Types)) // call binary functor
@@ -353,7 +385,7 @@ struct binary_dispatcher_lhs<F, V, T0, T1, Types...>
template<typename F, typename V, typename T>
struct binary_dispatcher_lhs<F, V, T>
{
- using result_type = typename F::result_type;
+ using result_type = typename detail::result_of_binary_visit<F, V>::type;
VARIANT_INLINE static result_type apply_const(V const&, V const&, F)
{
throw std::runtime_error("binary dispatch: FAIL");
@@ -371,7 +403,7 @@ struct binary_dispatcher;
template <typename F, typename V, typename T, typename...Types>
struct binary_dispatcher<F, V, T, Types...>
{
- using result_type = typename F::result_type;
+ using result_type = typename detail::result_of_binary_visit<F, V>::type;
VARIANT_INLINE static result_type apply_const(V const& v0, V const& v1, F f)
{
if (v0.get_type_index() == sizeof...(Types))
@@ -416,7 +448,7 @@ struct binary_dispatcher<F, V, T, Types...>
template<typename F, typename V>
struct binary_dispatcher<F, V>
{
- using result_type = typename F::result_type;
+ using result_type = typename detail::result_of_binary_visit<F, V>::type;
VARIANT_INLINE static result_type apply_const(V const&, V const&, F)
{
throw std::runtime_error("binary dispatch: FAIL");
@@ -448,7 +480,7 @@ struct less_comp
};
template <typename Variant, typename Comp>
-class comparer : public static_visitor<bool>
+class comparer
{
public:
explicit comparer(Variant const& lhs) noexcept
@@ -467,7 +499,7 @@ private:
// operator<< helper
template <typename Out>
-class printer : public static_visitor<>
+class printer
{
public:
explicit printer(Out & out)
diff --git a/ios/mapbox-gl-cocoa b/ios/mapbox-gl-cocoa
-Subproject 905bca9394a6e4d6a89dcf0e10b4e25105fbe64
+Subproject 37c9f726752674f87758cd351316413f978f160
diff --git a/linux/main.cpp b/linux/main.cpp
index 245cb527f0..12f77ffe58 100644
--- a/linux/main.cpp
+++ b/linux/main.cpp
@@ -4,7 +4,8 @@
#include <mbgl/platform/default/settings_json.hpp>
#include <mbgl/platform/default/glfw_view.hpp>
#include <mbgl/platform/default/log_stderr.hpp>
-#include <mbgl/storage/caching_http_file_source.hpp>
+#include <mbgl/storage/default_file_source.hpp>
+#include <mbgl/storage/default/sqlite_cache.hpp>
#include <signal.h>
#include <getopt.h>
@@ -64,7 +65,9 @@ int main(int argc, char *argv[]) {
sigaction(SIGINT, &sigIntHandler, NULL);
view = new GLFWView();
- mbgl::CachingHTTPFileSource fileSource(mbgl::platform::defaultCacheDatabase());
+
+ mbgl::SQLiteCache cache("/tmp/mbgl-cache.db");
+ mbgl::DefaultFileSource fileSource(&cache);
mbgl::Map map(*view, fileSource);
// Load settings
@@ -78,12 +81,13 @@ int main(int argc, char *argv[]) {
if (token == nullptr) {
mbgl::Log::Warning(mbgl::Event::Setup, "no access token set. mapbox.com tiles won't work.");
} else {
- fileSource.setAccessToken(std::string(token));
+ map.setAccessToken(std::string(token));
}
// Load style
- if (style.empty())
+ if (style.empty()) {
style = std::string("asset://") + std::string("styles/bright-v7.json");
+ }
map.setStyleURL(style);
diff --git a/linux/mapboxgl-app.gyp b/linux/mapboxgl-app.gyp
index 60eb16de38..afcc3a83a3 100644
--- a/linux/mapboxgl-app.gyp
+++ b/linux/mapboxgl-app.gyp
@@ -3,55 +3,52 @@
'../gyp/common.gypi',
],
'targets': [
- {
- 'target_name': 'linuxapp',
+ { 'target_name': 'linuxapp',
'product_name': 'mapbox-gl',
'type': 'executable',
+
+ 'dependencies': [
+ '../mbgl.gyp:core',
+ '../mbgl.gyp:platform-<(platform_lib)',
+ '../mbgl.gyp:http-<(http_lib)',
+ '../mbgl.gyp:asset-<(asset_lib)',
+ '../mbgl.gyp:cache-<(cache_lib)',
+ '../mbgl.gyp:bundle_styles',
+ '../mbgl.gyp:copy_certificate_bundle',
+ ],
+
'sources': [
- './main.cpp',
+ 'main.cpp',
'../platform/default/settings_json.cpp',
'../platform/default/glfw_view.cpp',
'../platform/default/log_stderr.cpp',
],
- 'xcode_settings': {
- 'OTHER_CPLUSPLUSFLAGS':[
+
+ 'variables' : {
+ 'cflags_cc': [
'<@(glfw3_cflags)',
- '<@(png_cflags)',
- '<@(jpeg_cflags)',
],
- },
- 'cflags_cc': [
- '<@(glfw3_cflags)',
- '<@(png_cflags)',
- '<@(jpeg_cflags)',
- '-I<(boost_root)/include',
- ],
- 'variables': {
'ldflags': [
- '<@(png_ldflags)',
- '<@(sqlite3_static_libs)',
- '<@(sqlite3_ldflags)',
- '<@(glfw3_static_libs)',
'<@(glfw3_ldflags)',
- '<@(curl_ldflags)',
- '<@(zlib_ldflags)',
+ ],
+ 'libraries': [
+ '<@(glfw3_static_libs)',
],
},
+
'conditions': [
['OS == "mac"', {
+ 'libraries': [ '<@(libraries)' ],
'xcode_settings': {
+ 'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ],
'OTHER_LDFLAGS': [ '<@(ldflags)' ],
}
}, {
- 'libraries': [ '<@(ldflags)' ],
+ 'cflags_cc': [ '<@(cflags_cc)' ],
+ 'libraries': [ '<@(libraries)', '<@(ldflags)' ],
}]
],
- 'dependencies': [
- '../mapboxgl.gyp:mbgl-standalone',
- '../mapboxgl.gyp:mbgl-linux',
- '../mapboxgl.gyp:bundle_styles',
- '../mapboxgl.gyp:copy_certificate_bundle',
- ],
+
'copies': [{
'files': [ '../styles/styles' ],
'destination': '<(PRODUCT_DIR)'
diff --git a/macosx/main.mm b/macosx/main.mm
index 277f8c44d2..d61d7d16bf 100644
--- a/macosx/main.mm
+++ b/macosx/main.mm
@@ -3,7 +3,9 @@
#include <mbgl/platform/darwin/log_nslog.hpp>
#include <mbgl/platform/darwin/Reachability.h>
#include <mbgl/platform/default/glfw_view.hpp>
-#include <mbgl/storage/caching_http_file_source.hpp>
+#include <mbgl/storage/default_file_source.hpp>
+#include <mbgl/storage/default/sqlite_cache.hpp>
+#include <mbgl/storage/network_status.hpp>
#import <Foundation/Foundation.h>
@@ -70,13 +72,39 @@
}
@end
+// Returns the path to the default cache database on this system.
+const std::string &defaultCacheDatabase() {
+ static const std::string path = []() -> std::string {
+ NSArray *paths = NSSearchPathForDirectoriesInDomains(
+ NSApplicationSupportDirectory, NSUserDomainMask, YES);
+ if ([paths count] == 0) {
+ // Disable the cache if we don't have a location to write.
+ return "";
+ }
+
+ NSString *p = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"Mapbox GL"];
+
+ if (![[NSFileManager defaultManager] createDirectoryAtPath:p
+ withIntermediateDirectories:YES
+ attributes:nil
+ error:nil]) {
+ // Disable the cache if we couldn't create the directory.
+ return "";
+ }
+
+ return [[p stringByAppendingPathComponent:@"cache.db"] UTF8String];
+ }();
+ return path;
+}
+
int main() {
mbgl::Log::Set<mbgl::NSLogBackend>();
GLFWView view;
- mbgl::CachingHTTPFileSource fileSource(mbgl::platform::defaultCacheDatabase());
+
+ mbgl::SQLiteCache cache(defaultCacheDatabase());
+ mbgl::DefaultFileSource fileSource(&cache);
mbgl::Map map(view, fileSource);
- mbgl::CachingHTTPFileSource *fileSourcePtr = &fileSource;
URLHandler *handler = [[URLHandler alloc] init];
[handler setMap:&map];
@@ -86,7 +114,7 @@ int main() {
// Notify map object when network reachability status changes.
Reachability* reachability = [Reachability reachabilityForInternetConnection];
reachability.reachableBlock = ^(Reachability *) {
- fileSourcePtr->setReachability(true);
+ mbgl::NetworkStatus::Reachable();
};
[reachability startNotifier];
@@ -99,7 +127,7 @@ int main() {
// Set access token if present
NSString *accessToken = [[NSProcessInfo processInfo] environment][@"MAPBOX_ACCESS_TOKEN"];
if (!accessToken) mbgl::Log::Warning(mbgl::Event::Setup, "No access token set. Mapbox vector tiles won't work.");
- if (accessToken) fileSource.setAccessToken([accessToken cStringUsingEncoding:[NSString defaultCStringEncoding]]);
+ if (accessToken) map.setAccessToken([accessToken cStringUsingEncoding:[NSString defaultCStringEncoding]]);
// Load style
map.setStyleURL("asset://styles/bright-v7.json");
diff --git a/macosx/mapboxgl-app.gyp b/macosx/mapboxgl-app.gyp
index 192604d3cf..37280286e5 100644
--- a/macosx/mapboxgl-app.gyp
+++ b/macosx/mapboxgl-app.gyp
@@ -3,10 +3,24 @@
'../gyp/common.gypi',
],
'targets': [
- {
- 'target_name': 'osxapp',
+ { 'target_name': 'osxapp',
'product_name': 'Mapbox GL',
'type': 'executable',
+ 'product_extension': 'app',
+ 'mac_bundle': 1,
+ 'mac_bundle_resources': [
+ 'Icon.icns',
+ ],
+
+ 'dependencies': [
+ '../mbgl.gyp:bundle_styles',
+ '../mbgl.gyp:core',
+ '../mbgl.gyp:platform-<(platform_lib)',
+ '../mbgl.gyp:http-<(http_lib)',
+ '../mbgl.gyp:asset-<(asset_lib)',
+ '../mbgl.gyp:cache-<(cache_lib)',
+ ],
+
'sources': [
'./main.mm',
'../platform/darwin/settings_nsuserdefaults.hpp',
@@ -15,51 +29,34 @@
'../platform/default/glfw_view.hpp',
'../platform/default/glfw_view.cpp',
],
- 'product_extension': 'app',
- 'mac_bundle': 1,
- 'mac_bundle_resources': [
- 'Icon.icns',
+
+ 'variables' : {
+ 'cflags_cc': [
+ '<@(glfw3_cflags)',
+ ],
+ 'ldflags': [
+ '-framework SystemConfiguration', # For NSUserDefaults and Reachability
+ '<@(glfw3_ldflags)',
+ ],
+ 'libraries': [
+ '<@(glfw3_static_libs)',
+ ],
+ },
+
+ 'libraries': [
+ '<@(libraries)',
],
+
'xcode_settings': {
'SDKROOT': 'macosx',
'SUPPORTED_PLATFORMS':'macosx',
- 'OTHER_CPLUSPLUSFLAGS': [
- '<@(glfw3_cflags)'
- ],
- 'OTHER_LDFLAGS': [
- '<@(glfw3_ldflags)',
- '-framework SystemConfiguration',
- ],
+ 'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ],
+ 'OTHER_LDFLAGS': [ '<@(ldflags)' ],
'SDKROOT': 'macosx',
'INFOPLIST_FILE': 'Info.plist',
- 'MACOSX_DEPLOYMENT_TARGET':'10.9',
+ 'MACOSX_DEPLOYMENT_TARGET': '10.9',
'CLANG_ENABLE_OBJC_ARC': 'YES'
},
- 'variables' : {
- 'ldflags': [
- '<@(uv_ldflags)',
- '<@(uv_static_libs)',
- '<@(sqlite3_static_libs)',
- '<@(sqlite3_ldflags)',
- '<@(glfw3_static_libs)',
- '<@(glfw3_ldflags)',
- '<@(zlib_ldflags)',
- ]
- },
- 'conditions': [
- ['OS == "mac"', {
- 'xcode_settings': {
- 'OTHER_LDFLAGS': [ '<@(ldflags)' ],
- }
- }, {
- 'ldflags': [ '<@(ldflags)' ],
- }]
- ],
- 'dependencies': [
- '../mapboxgl.gyp:bundle_styles',
- '../mapboxgl.gyp:mbgl-core',
- '../mapboxgl.gyp:mbgl-osx',
- ]
}
]
}
diff --git a/mapboxgl.gyp b/mapboxgl.gyp
deleted file mode 100644
index 7ce215dc6a..0000000000
--- a/mapboxgl.gyp
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- 'includes': [
- './gyp/common.gypi',
- './gyp/shaders.gypi',
- './gyp/version.gypi',
- './gyp/styles.gypi',
- './gyp/certificates.gypi',
- './gyp/mbgl-core.gypi',
- './gyp/mbgl-platform.gypi',
- './gyp/mbgl-headless.gypi',
- './gyp/install.gypi',
- ],
-}
diff --git a/mbgl.gyp b/mbgl.gyp
new file mode 100644
index 0000000000..b6b00455e1
--- /dev/null
+++ b/mbgl.gyp
@@ -0,0 +1,26 @@
+{
+ 'includes': [
+ './gyp/common.gypi',
+ './gyp/shaders.gypi',
+ './gyp/version.gypi',
+ './gyp/styles.gypi',
+ './gyp/certificates.gypi',
+ './gyp/standalone.gypi',
+ './gyp/core.gypi',
+ './gyp/none.gypi',
+ './gyp/install.gypi',
+ ],
+ 'conditions': [
+ ['headless_lib == "cgl"', { 'includes': [ './gyp/headless-cgl.gypi' ] } ],
+ ['headless_lib == "glx"', { 'includes': [ './gyp/headless-glx.gypi' ] } ],
+ ['platform_lib == "osx"', { 'includes': [ './gyp/platform-osx.gypi' ] } ],
+ ['platform_lib == "ios"', { 'includes': [ './gyp/platform-ios.gypi' ] } ],
+ ['platform_lib == "linux"', { 'includes': [ './gyp/platform-linux.gypi' ] } ],
+ ['platform_lib == "android"', { 'includes': [ './gyp/platform-android.gypi' ] } ],
+ ['http_lib == "curl"', { 'includes': [ './gyp/http-curl.gypi' ] } ],
+ ['http_lib == "nsurl"', { 'includes': [ './gyp/http-nsurl.gypi' ] } ],
+ ['asset_lib == "fs"', { 'includes': [ './gyp/asset-fs.gypi' ] } ],
+ ['asset_lib == "zip"', { 'includes': [ './gyp/asset-zip.gypi' ] } ],
+ ['cache_lib == "sqlite"', { 'includes': [ './gyp/cache-sqlite.gypi' ] } ],
+ ],
+}
diff --git a/platform/android/asset_request_libzip.cpp b/platform/android/asset_request_libzip.cpp
deleted file mode 100644
index 77f9ae7da1..0000000000
--- a/platform/android/asset_request_libzip.cpp
+++ /dev/null
@@ -1,202 +0,0 @@
-#include <mbgl/android/jni.hpp>
-#include <mbgl/storage/asset_request.hpp>
-#include <mbgl/storage/response.hpp>
-#include <mbgl/util/uv_detail.hpp>
-#include <mbgl/util/std.hpp>
-
-#include <cerrno>
-// NOTE a bug in the Android NDK breaks std::errno
-// See https://code.google.com/p/android/issues/detail?id=72349
-// After this is fixed change usage errno to std::errno
-#include <zip.h>
-
-namespace mbgl {
-
-struct AssetRequestBaton {
- AssetRequestBaton(AssetRequest *request_, const std::string &path, uv_loop_t *loop);
- ~AssetRequestBaton();
-
- const std::thread::id threadId;
- AssetRequest *request = nullptr;
- std::unique_ptr<uv::async> asyncRun;
- std::string path;
- bool canceled = false;
-
- void cancel();
- static void notifyError(AssetRequestBaton *ptr, const int code, const char *message);
- static void notifySuccess(AssetRequestBaton *ptr, const std::string body);
- static void cleanup(AssetRequestBaton *ptr);
- static void run(AssetRequestBaton *ptr);
-};
-
-AssetRequestBaton::AssetRequestBaton(AssetRequest *request_, const std::string &path_, uv_loop_t *loop)
-: threadId(std::this_thread::get_id()),
-request(request_),
-path(path_) {
-
- asyncRun = mbgl::util::make_unique<uv::async>(loop, [this]() {
- run(this);
- });
-
- asyncRun->send();
-}
-
-AssetRequestBaton::~AssetRequestBaton() {
-}
-
-void AssetRequestBaton::cancel() {
- canceled = true;
-}
-
-void AssetRequestBaton::notifyError(AssetRequestBaton *ptr, const int code, const char *message) {
- assert(std::this_thread::get_id() == ptr->threadId);
-
- if (ptr->request && !ptr->canceled) {
- ptr->request->response = std::unique_ptr<Response>(new Response);
- ptr->request->response->code = code;
- ptr->request->response->message = message;
- ptr->request->notify();
- }
-}
-
-void AssetRequestBaton::notifySuccess(AssetRequestBaton *ptr, const std::string body) {
- assert(std::this_thread::get_id() == ptr->threadId);
-
- if (ptr->request && !ptr->canceled) {
- ptr->request->response = std::unique_ptr<Response>(new Response);
- ptr->request->response->code = 200;
- ptr->request->response->data = body;
- ptr->request->notify();
- }
-}
-
-void AssetRequestBaton::cleanup(AssetRequestBaton *ptr) {
- assert(std::this_thread::get_id() == ptr->threadId);
-
- if (ptr->request) {
- ptr->request->ptr = nullptr;
- }
-
- ptr->asyncRun.reset();
-
- delete ptr;
- ptr = nullptr;
-}
-
-void AssetRequestBaton::run(AssetRequestBaton *ptr) {
- assert(std::this_thread::get_id() == ptr->threadId);
-
- if (ptr->canceled || !ptr->request) {
- // Either the AssetRequest object has been destructed, or the
- // request was canceled.
- cleanup(ptr);
- return;
- }
-
- int error = 0;
- struct zip *apk = zip_open(mbgl::android::apkPath.c_str(), 0, &error);
- if ((apk == nullptr) || ptr->canceled || !ptr->request) {
- // Opening the APK failed or was canceled. There isn't much left we can do.
- const int messageSize = zip_error_to_str(nullptr, 0, error, errno);
- const std::unique_ptr<char[]> message = mbgl::util::make_unique<char[]>(messageSize);
- zip_error_to_str(message.get(), 0, error, errno);
- notifyError(ptr, 500, message.get());
- cleanup(ptr);
- return;
- }
-
- std::string apkFilePath = "assets/" + ptr->path;
- struct zip_file *apkFile = zip_fopen(apk, apkFilePath.c_str(), ZIP_FL_NOCASE);
- if ((apkFile == nullptr) || ptr->canceled || !ptr->request) {
- // Opening the asset failed or was canceled. We already have an open file handle
- // though, which we'll have to close.
- zip_error_get(apk, &error, nullptr);
- notifyError(ptr, error == ZIP_ER_NOENT ? 404 : 500, zip_strerror(apk));
- zip_close(apk);
- apk = nullptr;
- cleanup(ptr);
- return;
- }
-
- struct zip_stat stat;
- if ((zip_stat(apk, apkFilePath.c_str(), ZIP_FL_NOCASE, &stat) != 0) || ptr->canceled || !ptr->request) {
- // Stating failed or was canceled. We already have an open file handle
- // though, which we'll have to close.
- notifyError(ptr, 500, zip_strerror(apk));
- zip_fclose(apkFile);
- apkFile = nullptr;
- zip_close(apk);
- apk = nullptr;
- cleanup(ptr);
- return;
- }
-
- const std::unique_ptr<char[]> data = mbgl::util::make_unique<char[]>(stat.size);
-
- if (static_cast<zip_uint64_t>(zip_fread(apkFile, reinterpret_cast<void *>(data.get()), stat.size)) != stat.size || ptr->canceled || !ptr->request) {
- // Reading failed or was canceled. We already have an open file handle
- // though, which we'll have to close.
- notifyError(ptr, 500, zip_file_strerror(apkFile));
- zip_fclose(apkFile);
- apkFile = nullptr;
- zip_close(apk);
- apk = nullptr;
- cleanup(ptr);
- return;
- }
-
- std::string body(data.get(), stat.size);
- notifySuccess(ptr, body);
-
- if (zip_fclose(apkFile) != 0) {
- // Closing the asset failed. But there isn't anything we can do.
- }
- apkFile = nullptr;
-
- if (zip_close(apk) != 0) {
- // Closing the APK failed. But there isn't anything we can do.
- }
- apk = nullptr;
-
- cleanup(ptr);
-}
-
-AssetRequest::AssetRequest(const std::string &path_, uv_loop_t *loop)
-: BaseRequest(path_) {
- if (!path.empty() && path[0] == '/') {
- // This is an absolute path. We don't allow this. Note that this is not a way to absolutely
- // prevent access to resources outside the application bundle; e.g. there could be symlinks
- // in the application bundle that link to outside. We don't care about these.
- response = util::make_unique<Response>();
- response->code = 403;
- response->message = "Path is outside the application bundle";
- notify();
- } else {
- // Note: The AssetRequestBaton object is deleted in AssetRequestBaton::cleanup().
- ptr = new AssetRequestBaton(this, path, loop);
- }
-}
-
-void AssetRequest::cancel() {
- assert(std::this_thread::get_id() == threadId);
-
- if (ptr) {
- ptr->cancel();
-
- // When deleting a AssetRequest 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;
- ptr = nullptr;
- }
-
- notify();
-}
-
-AssetRequest::~AssetRequest() {
- assert(std::this_thread::get_id() == threadId);
- cancel();
-
- // Note: The AssetRequestBaton object is deleted in AssetRequestBaton::cleanup().
-}
-
-}
diff --git a/platform/android/cache_database_data.cpp b/platform/android/cache_database_data.cpp
deleted file mode 100644
index 2fefcdc4a3..0000000000
--- a/platform/android/cache_database_data.cpp
+++ /dev/null
@@ -1,13 +0,0 @@
-#include <mbgl/platform/platform.hpp>
-#include <mbgl/android/jni.hpp>
-
-namespace mbgl {
-namespace platform {
-
-// Returns the path to the default cache database on this system.
-std::string defaultCacheDatabase() {
- return mbgl::android::cachePath + "/mbgl-cache.db";
-}
-
-}
-}
diff --git a/platform/darwin/application_root.mm b/platform/darwin/application_root.mm
index 19b872c54d..d4702c7ec5 100644
--- a/platform/darwin/application_root.mm
+++ b/platform/darwin/application_root.mm
@@ -5,8 +5,8 @@
namespace mbgl {
namespace platform {
-// Returns the path to the default shader cache on this system.
-std::string applicationRoot() {
+// Returns the path to the root folder of the application.
+const std::string &applicationRoot() {
static const std::string root = []() -> std::string {
NSString *path = [[[NSBundle mainBundle] resourceURL] path];
return {[path cStringUsingEncoding : NSUTF8StringEncoding],
diff --git a/platform/darwin/asset_root.mm b/platform/darwin/asset_root.mm
new file mode 100644
index 0000000000..375975a84b
--- /dev/null
+++ b/platform/darwin/asset_root.mm
@@ -0,0 +1,18 @@
+#import <Foundation/Foundation.h>
+
+#include <mbgl/platform/platform.hpp>
+
+namespace mbgl {
+namespace platform {
+
+// Returns the path to the root folder of the application.
+const std::string &assetRoot() {
+ static const std::string root = []() -> std::string {
+ NSString *path = [[[NSBundle mainBundle] resourceURL] path];
+ return {[path cStringUsingEncoding : NSUTF8StringEncoding],
+ [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding]};
+ }();
+ return root;
+}
+}
+}
diff --git a/platform/darwin/http_request_baton_cocoa.mm b/platform/darwin/http_request_baton_cocoa.mm
deleted file mode 100644
index 4a59837e32..0000000000
--- a/platform/darwin/http_request_baton_cocoa.mm
+++ /dev/null
@@ -1,155 +0,0 @@
-#include <mbgl/storage/http_request_baton.hpp>
-#include <mbgl/util/std.hpp>
-#include <mbgl/util/parsedate.h>
-#include <mbgl/util/time.hpp>
-#include <mbgl/util/version.hpp>
-
-#include <uv.h>
-
-#include <mbgl/util/uv.hpp>
-
-#import <Foundation/Foundation.h>
-
-namespace mbgl {
-
-dispatch_once_t request_initialize = 0;
-NSURLSession *session = nullptr;
-
-NSString *userAgent = nil;
-
-void HTTPRequestBaton::start(const util::ptr<HTTPRequestBaton> &ptr) {
- assert(std::this_thread::get_id() == ptr->threadId);
-
- // Starts the request.
- util::ptr<HTTPRequestBaton> baton = ptr;
-
- dispatch_once(&request_initialize, ^{
- NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
- sessionConfig.timeoutIntervalForResource = 30;
- sessionConfig.HTTPMaximumConnectionsPerHost = 8;
- sessionConfig.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
- sessionConfig.URLCache = nil;
-
- session = [NSURLSession sessionWithConfiguration:sessionConfig];
-
- // Write user agent string
- NSDictionary *systemVersion = [NSDictionary dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"];
- userAgent = [NSString stringWithFormat:@"MapboxGL/%d.%d.%d (+https://mapbox.com/mapbox-gl/; %s; %@ %@)",
- version::major, version::minor, version::patch, version::revision,
- [systemVersion objectForKey:@"ProductName"],
- [systemVersion objectForKey:@"ProductVersion"]
- ];
- });
-
- NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@(baton->path.c_str())]];
- if (baton->response) {
- if (!baton->response->etag.empty()) {
- [request addValue:@(baton->response->etag.c_str()) forHTTPHeaderField:@"If-None-Match"];
- } else if (baton->response->modified) {
- const std::string time = util::rfc1123(baton->response->modified);
- [request addValue:@(time.c_str()) forHTTPHeaderField:@"If-Modified-Since"];
- }
- }
-
- [request addValue:userAgent forHTTPHeaderField:@"User-Agent"];
-
- NSURLSessionDataTask *task = [session dataTaskWithRequest:request
- completionHandler:^(NSData *data, NSURLResponse *res, NSError *error) {
- if (error) {
- if ([error code] == NSURLErrorCancelled) {
- // The response code remains at 0 to indicate cancelation.
- // In addition, we don't need any response object.
- baton->response.reset();
- baton->type = HTTPResponseType::Canceled;
- } else {
- // TODO: Use different codes for host not found, timeout, invalid URL etc.
- // These can be categorized in temporary and permanent errors.
- baton->response = util::make_unique<Response>();
- baton->response->code = [(NSHTTPURLResponse *)res statusCode];
- baton->response->message = [[error localizedDescription] UTF8String];
-
- switch ([error code]) {
- case NSURLErrorBadServerResponse: // 5xx errors
- baton->type = HTTPResponseType::TemporaryError;
- break;
-
- case NSURLErrorTimedOut:
- case NSURLErrorUserCancelledAuthentication:
- baton->type = HTTPResponseType::SingularError; // retry immediately
- break;
-
- case NSURLErrorNetworkConnectionLost:
- case NSURLErrorCannotFindHost:
- case NSURLErrorCannotConnectToHost:
- case NSURLErrorDNSLookupFailed:
- case NSURLErrorNotConnectedToInternet:
- case NSURLErrorInternationalRoamingOff:
- case NSURLErrorCallIsActive:
- case NSURLErrorDataNotAllowed:
- baton->type = HTTPResponseType::ConnectionError;
- break;
-
- default:
- baton->type = HTTPResponseType::PermanentError;
- }
- }
- } else if ([res isKindOfClass:[NSHTTPURLResponse class]]) {
- const long code = [(NSHTTPURLResponse *)res statusCode];
- if (code == 304) {
- // Assume a Response object already exists.
- assert(baton->response);
- } else {
- baton->response = util::make_unique<Response>();
- baton->response->code = code;
- baton->response->data = {(const char *)[data bytes], [data length]};
- }
-
- if (code == 304) {
- baton->type = HTTPResponseType::NotModified;
- } else if (code == 200) {
- baton->type = HTTPResponseType::Successful;
- } else {
- baton->type = HTTPResponseType::PermanentError;
- }
-
- NSDictionary *headers = [(NSHTTPURLResponse *)res allHeaderFields];
- NSString *cache_control = [headers objectForKey:@"Cache-Control"];
- if (cache_control) {
- baton->response->expires = Response::parseCacheControl([cache_control UTF8String]);
- }
-
- NSString *last_modified = [headers objectForKey:@"Last-Modified"];
- if (last_modified) {
- baton->response->modified = parse_date([last_modified UTF8String]);
- }
-
- NSString *etag = [headers objectForKey:@"ETag"];
- if (etag) {
- baton->response->etag = [etag UTF8String];
- }
- } else {
- // This should never happen.
- baton->type = HTTPResponseType::PermanentError;
- baton->response = util::make_unique<Response>();
- baton->response->code = -1;
- baton->response->message = "response class is not NSHTTPURLResponse";
- }
-
- uv_async_send(baton->async);
- }];
-
- [task resume];
-
- baton->ptr = const_cast<void *>(CFBridgingRetain(task));
-}
-
-void HTTPRequestBaton::stop(const util::ptr<HTTPRequestBaton> &ptr) {
- assert(std::this_thread::get_id() == ptr->threadId);
- assert(ptr->ptr);
-
- NSURLSessionDataTask *task = CFBridgingRelease(ptr->ptr);
- ptr->ptr = nullptr;
- [task cancel];
-}
-
-}
diff --git a/platform/darwin/http_request_nsurl.mm b/platform/darwin/http_request_nsurl.mm
new file mode 100644
index 0000000000..83c010f8b8
--- /dev/null
+++ b/platform/darwin/http_request_nsurl.mm
@@ -0,0 +1,419 @@
+#include <mbgl/storage/default/http_request.hpp>
+#include <mbgl/storage/default/http_context.hpp>
+#include <mbgl/storage/response.hpp>
+#include <mbgl/util/uv.hpp>
+
+#include <mbgl/util/time.hpp>
+#include <mbgl/util/parsedate.h>
+
+#import <Foundation/Foundation.h>
+
+#include <map>
+#include <cassert>
+
+namespace mbgl {
+
+enum class ResponseStatus : uint8_t {
+ // This error probably won't be resolved by retrying anytime soon. We are giving up.
+ PermanentError,
+
+ // This error might be resolved by waiting some time (e.g. server issues).
+ // We are going to do an exponential back-off and will try again in a few seconds.
+ TemporaryError,
+
+ // This error was caused by a temporary error and it is likely that it will be resolved
+ // immediately. We are going to try again right away. This is like the TemporaryError, except
+ // that we will not perform exponential back-off.
+ SingularError,
+
+ // This error might be resolved once the network reachability status changes.
+ // We are going to watch the network status for changes and will retry as soon as the
+ // operating system notifies us of a network status change.
+ ConnectionError,
+
+ // The request was canceled mid-way.
+ Canceled,
+
+ // The request returned data successfully. We retrieved and decoded the data successfully.
+ Successful,
+
+ // The request confirmed that the data wasn't changed. We already have the data.
+ NotModified,
+};
+
+// -------------------------------------------------------------------------------------------------
+
+class HTTPNSURLContext;
+
+class HTTPRequestImpl {
+public:
+ HTTPRequestImpl(HTTPRequest *request, uv_loop_t *loop, std::unique_ptr<Response> response);
+ ~HTTPRequestImpl();
+
+ void cancel();
+ void cancelTimer();
+
+ void start();
+ void handleResult(NSData *data, NSURLResponse *res, NSError *error);
+ void handleResponse();
+
+ void retry(uint64_t timeout);
+ void retryImmediately();
+ static void restart(uv_timer_t *timer, int);
+
+private:
+ HTTPNSURLContext *context = nullptr;
+ HTTPRequest *request = nullptr;
+ NSURLSessionDataTask *task = nullptr;
+ std::unique_ptr<Response> response;
+ std::unique_ptr<Response> existingResponse;
+ ResponseStatus status = ResponseStatus::PermanentError;
+ uv_async_t *async = nullptr;
+ int attempts = 0;
+ uv_timer_t *timer = nullptr;
+ enum : bool { PreemptImmediately, ExponentialBackoff } strategy = PreemptImmediately;
+
+ static const int maxAttempts = 4;
+};
+
+// -------------------------------------------------------------------------------------------------
+
+class HTTPNSURLContext : public HTTPContext<HTTPNSURLContext> {
+public:
+ HTTPNSURLContext(uv_loop_t *loop);
+ ~HTTPNSURLContext();
+
+ NSURLSession *session = nil;
+ NSString *userAgent = nil;
+};
+
+template<> pthread_key_t ThreadContext<HTTPNSURLContext>::key{};
+template<> pthread_once_t ThreadContext<HTTPNSURLContext>::once = PTHREAD_ONCE_INIT;
+
+HTTPNSURLContext::HTTPNSURLContext(uv_loop_t *loop_) : HTTPContext(loop_) {
+ @autoreleasepool {
+ NSURLSessionConfiguration *sessionConfig =
+ [NSURLSessionConfiguration defaultSessionConfiguration];
+ sessionConfig.timeoutIntervalForResource = 30;
+ sessionConfig.HTTPMaximumConnectionsPerHost = 8;
+ sessionConfig.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
+ sessionConfig.URLCache = nil;
+
+ session = [NSURLSession sessionWithConfiguration:sessionConfig];
+ [session retain];
+
+ // Write user agent string
+ userAgent = @"MapboxGL";
+ }
+}
+
+HTTPNSURLContext::~HTTPNSURLContext() {
+ [session release];
+ session = nullptr;
+
+ [userAgent release];
+ userAgent = nullptr;
+}
+
+// -------------------------------------------------------------------------------------------------
+
+HTTPRequestImpl::HTTPRequestImpl(HTTPRequest *request_, uv_loop_t *loop,
+ std::unique_ptr<Response> existingResponse_)
+ : context(HTTPNSURLContext::Get(loop)),
+ request(request_),
+ existingResponse(std::move(existingResponse_)),
+ async(new uv_async_t) {
+ assert(request);
+ context->addRequest(request);
+
+ async->data = this;
+ uv_async_init(loop, async, [](uv_async_t *as, int) {
+ auto impl = reinterpret_cast<HTTPRequestImpl *>(as->data);
+ impl->handleResponse();
+ });
+
+ start();
+}
+
+void HTTPRequestImpl::start() {
+ assert(!task);
+
+ attempts++;
+
+ @autoreleasepool {
+ NSMutableURLRequest *req = [[NSMutableURLRequest alloc]
+ initWithURL:[NSURL URLWithString:@(request->resource.url.c_str())]];
+ if (existingResponse) {
+ if (!existingResponse->etag.empty()) {
+ [req addValue:@(existingResponse->etag.c_str()) forHTTPHeaderField:@"If-None-Match"];
+ } else if (existingResponse->modified) {
+ const std::string time = util::rfc1123(existingResponse->modified);
+ [req addValue:@(time.c_str()) forHTTPHeaderField:@"If-Modified-Since"];
+ }
+ }
+
+ [req addValue:context->userAgent forHTTPHeaderField:@"User-Agent"];
+
+ task = [context->session dataTaskWithRequest:req
+ completionHandler:^(NSData *data, NSURLResponse *res,
+ NSError *error) { handleResult(data, res, error); }];
+ [req release];
+ [task retain];
+ [task resume];
+ }
+}
+
+void HTTPRequestImpl::handleResponse() {
+ if (task) {
+ [task release];
+ task = nullptr;
+ }
+
+ if (request) {
+ if (status == ResponseStatus::TemporaryError && attempts < maxAttempts) {
+ strategy = ExponentialBackoff;
+ return retry((1 << (attempts - 1)) * 1000);
+ } else if (status == ResponseStatus::ConnectionError && attempts < maxAttempts) {
+ // By default, we will retry every 30 seconds (network change notification will
+ // preempt the timeout).
+ strategy = PreemptImmediately;
+ return retry(30000);
+ }
+
+ // Actually return the response.
+ if (status == ResponseStatus::NotModified) {
+ request->notify(std::move(response), FileCache::Hint::Refresh);
+ } else {
+ request->notify(std::move(response), FileCache::Hint::Full);
+ }
+
+ context->removeRequest(request);
+ delete request;
+ request = nullptr;
+ }
+
+ delete this;
+}
+
+void HTTPRequestImpl::cancel() {
+ context->removeRequest(request);
+ request = nullptr;
+
+ // Stop the backoff timer to avoid re-triggering this request.
+ cancelTimer();
+
+ if (task) {
+ [task cancel];
+ [task release];
+ task = nullptr;
+ }
+}
+
+void HTTPRequestImpl::cancelTimer() {
+ if (timer) {
+ uv_timer_stop(timer);
+ uv::close(timer);
+ timer = nullptr;
+ }
+}
+
+HTTPRequestImpl::~HTTPRequestImpl() {
+ assert(!task);
+ assert(async);
+
+ // Stop the backoff timer to avoid re-triggering this request.
+ cancelTimer();
+
+ uv::close(async);
+
+ if (request) {
+ context->removeRequest(request);
+ request->ptr = nullptr;
+ }
+}
+
+int64_t parseCacheControl(const char *value) {
+ if (value) {
+ unsigned long long 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 0;
+}
+
+void HTTPRequestImpl::handleResult(NSData *data, NSURLResponse *res, NSError *error) {
+ if (error) {
+ if ([error code] == NSURLErrorCancelled) {
+ status = ResponseStatus::Canceled;
+ } else {
+ // TODO: Use different codes for host not found, timeout, invalid URL etc.
+ // These can be categorized in temporary and permanent errors.
+ response = util::make_unique<Response>();
+ response->status = Response::Error;
+ response->message = [[error localizedDescription] UTF8String];
+
+ switch ([error code]) {
+ case NSURLErrorBadServerResponse: // 5xx errors
+ status = ResponseStatus::TemporaryError;
+ break;
+
+ case NSURLErrorTimedOut:
+ case NSURLErrorUserCancelledAuthentication:
+ status = ResponseStatus::SingularError; // retry immediately
+ break;
+
+ case NSURLErrorNetworkConnectionLost:
+ case NSURLErrorCannotFindHost:
+ case NSURLErrorCannotConnectToHost:
+ case NSURLErrorDNSLookupFailed:
+ case NSURLErrorNotConnectedToInternet:
+ case NSURLErrorInternationalRoamingOff:
+ case NSURLErrorCallIsActive:
+ case NSURLErrorDataNotAllowed:
+ status = ResponseStatus::ConnectionError;
+ break;
+
+ default:
+ status = ResponseStatus::PermanentError;
+ }
+ }
+ } else if ([res isKindOfClass:[NSHTTPURLResponse class]]) {
+ const long responseCode = [(NSHTTPURLResponse *)res statusCode];
+
+ response = util::make_unique<Response>();
+ 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 = parseCacheControl([cache_control UTF8String]);
+ }
+
+ NSString *expires = [headers objectForKey:@"Expires"];
+ if (expires) {
+ response->expires = parse_date([expires UTF8String]);
+ }
+
+ NSString *last_modified = [headers objectForKey:@"Last-Modified"];
+ if (last_modified) {
+ response->modified = parse_date([last_modified UTF8String]);
+ }
+
+ NSString *etag = [headers objectForKey:@"ETag"];
+ if (etag) {
+ response->etag = [etag UTF8String];
+ }
+
+ if (responseCode == 304) {
+ if (existingResponse) {
+ // We're going to reuse the old response object, but need to copy over the new
+ // expires value (if possible).
+ std::swap(response, existingResponse);
+ if (existingResponse->expires) {
+ response->expires = existingResponse->expires;
+ }
+ status = ResponseStatus::NotModified;
+ } else {
+ // This is an unsolicited 304 response and should only happen on malfunctioning
+ // HTTP servers. It likely doesn't include any data, but we don't have much options.
+ response->status = Response::Successful;
+ status = ResponseStatus::Successful;
+ }
+ } else if (responseCode == 200) {
+ response->status = Response::Successful;
+ status = ResponseStatus::Successful;
+ } else if (responseCode >= 500 && responseCode < 600) {
+ // Server errors may be temporary, so back off exponentially.
+ response->status = Response::Error;
+ response->message = "HTTP status code " + std::to_string(responseCode);
+ status = ResponseStatus::TemporaryError;
+ } else {
+ // We don't know how to handle any other errors, so declare them as permanently failing.
+ response->status = Response::Error;
+ response->message = "HTTP status code " + std::to_string(responseCode);
+ status = ResponseStatus::PermanentError;
+ }
+ } else {
+ // This should never happen.
+ status = ResponseStatus::PermanentError;
+ response = util::make_unique<Response>();
+ response->status = Response::Error;
+ response->message = "response class is not NSHTTPURLResponse";
+ }
+
+ uv_async_send(async);
+}
+
+void HTTPRequestImpl::retry(uint64_t timeout) {
+ response.reset();
+
+ assert(!timer);
+ timer = new uv_timer_t;
+ timer->data = this;
+ uv_timer_init(async->loop, timer);
+ uv_timer_start(timer, restart, timeout, 0);
+}
+
+void HTTPRequestImpl::retryImmediately() {
+ // All batons get notified when the network status changed, but some of them
+ // might not actually wait for the network to become available again.
+ if (timer && strategy == PreemptImmediately) {
+ // Triggers the timer upon the next event loop iteration.
+ uv_timer_stop(timer);
+ uv_timer_start(timer, restart, 0, 0);
+ }
+}
+
+void HTTPRequestImpl::restart(uv_timer_t *timer, int) {
+ // Restart the request.
+ auto impl = reinterpret_cast<HTTPRequestImpl *>(timer->data);
+
+ // Get rid of the timer.
+ impl->timer = nullptr;
+ uv::close(timer);
+
+ impl->start();
+}
+
+// -------------------------------------------------------------------------------------------------
+
+HTTPRequest::HTTPRequest(DefaultFileSource *source, const Resource &resource)
+ : SharedRequestBase(source, resource) {
+}
+
+HTTPRequest::~HTTPRequest() {
+ MBGL_VERIFY_THREAD(tid);
+
+ if (ptr) {
+ reinterpret_cast<HTTPRequestImpl *>(ptr)->cancel();
+ }
+}
+
+void HTTPRequest::start(uv_loop_t *loop, std::unique_ptr<Response> response) {
+ MBGL_VERIFY_THREAD(tid);
+
+ assert(!ptr);
+ ptr = new HTTPRequestImpl(this, loop, std::move(response));
+}
+
+void HTTPRequest::retryImmediately() {
+ MBGL_VERIFY_THREAD(tid);
+
+ if (ptr) {
+ reinterpret_cast<HTTPRequestImpl *>(ptr)->retryImmediately();
+ }
+}
+
+void HTTPRequest::cancel() {
+ MBGL_VERIFY_THREAD(tid);
+
+ delete this;
+}
+
+}
diff --git a/platform/darwin/log_nslog.mm b/platform/darwin/log_nslog.mm
index b196930b23..ea5fddf0b9 100644
--- a/platform/darwin/log_nslog.mm
+++ b/platform/darwin/log_nslog.mm
@@ -7,14 +7,13 @@
namespace mbgl {
void NSLogBackend::record(EventSeverity severity, Event event, const std::string &msg) {
- NSLog(@"[%s] %s: %@", EventSeverityClass(severity).c_str(), EventClass(event).c_str(),
- [[NSString alloc] initWithBytes:msg.data()
- length:msg.size()
- encoding:NSUTF8StringEncoding]);
+ NSString *message =
+ [[NSString alloc] initWithBytes:msg.data() length:msg.size() encoding:NSUTF8StringEncoding];
+ NSLog(@"[%s] %s: %@", EventSeverityClass(severity).c_str(), EventClass(event).c_str(), message);
+ [message release];
}
-
-void NSLogBackend::record(EventSeverity severity, Event event, const char* format, ...) {
+void NSLogBackend::record(EventSeverity severity, Event event, const char *format, ...) {
va_list args;
va_start(args, format);
const size_t len = vsnprintf(NULL, 0, format, args);
@@ -23,17 +22,22 @@ void NSLogBackend::record(EventSeverity severity, Event event, const char* forma
va_start(args, format);
vsnprintf(buffer.get(), len + 1, format, args);
va_end(args);
- NSLog(@"[%s] %s: %s", EventSeverityClass(severity).c_str(), EventClass(event).c_str(), buffer.get());
+ NSLog(@"[%s] %s: %s", EventSeverityClass(severity).c_str(), EventClass(event).c_str(),
+ buffer.get());
}
void NSLogBackend::record(EventSeverity severity, Event event, int64_t code) {
- NSLog(@"[%s] %s: (%lld)", EventSeverityClass(severity).c_str(), EventClass(event).c_str(), code);
+ NSLog(@"[%s] %s: (%lld)", EventSeverityClass(severity).c_str(), EventClass(event).c_str(),
+ code);
}
-void NSLogBackend::record(EventSeverity severity, Event event, int64_t code, const std::string &msg) {
- NSLog(@"[%s] %s: (%lld) %@", EventSeverityClass(severity).c_str(), EventClass(event).c_str(), code,
- [[NSString alloc] initWithBytes:msg.data()
- length:msg.size()
- encoding:NSUTF8StringEncoding]);
+void NSLogBackend::record(EventSeverity severity, Event event, int64_t code,
+ const std::string &msg) {
+ NSString *message =
+ [[NSString alloc] initWithBytes:msg.data() length:msg.size() encoding:NSUTF8StringEncoding];
+ NSLog(@"[%s] %s: (%lld) %@", EventSeverityClass(severity).c_str(), EventClass(event).c_str(),
+ code, message);
+ [message release];
}
+
}
diff --git a/platform/darwin/string_nsstring.mm b/platform/darwin/string_nsstring.mm
index 9b1dc745cb..86c2c07edd 100644
--- a/platform/darwin/string_nsstring.mm
+++ b/platform/darwin/string_nsstring.mm
@@ -6,15 +6,27 @@ namespace mbgl {
namespace platform {
std::string uppercase(const std::string &string) {
- NSString *nsstring = [[NSString alloc] initWithBytesNoCopy:const_cast<char *>(string.data()) length:string.size() encoding:NSUTF8StringEncoding freeWhenDone:NO];
- nsstring = [nsstring uppercaseString];
- return { [nsstring cStringUsingEncoding:NSUTF8StringEncoding], [nsstring lengthOfBytesUsingEncoding:NSUTF8StringEncoding] };
+ NSString *original = [[NSString alloc] initWithBytesNoCopy:const_cast<char *>(string.data())
+ length:string.size()
+ encoding:NSUTF8StringEncoding
+ freeWhenDone:NO];
+ NSString *uppercase = [original uppercaseString];
+ const std::string result{[uppercase cStringUsingEncoding : NSUTF8StringEncoding],
+ [uppercase lengthOfBytesUsingEncoding:NSUTF8StringEncoding]};
+ [original release];
+ return result;
}
std::string lowercase(const std::string &string) {
- NSString *nsstring = [[NSString alloc] initWithBytesNoCopy:const_cast<char *>(string.data()) length:string.size() encoding:NSUTF8StringEncoding freeWhenDone:NO];
- nsstring = [nsstring lowercaseString];
- return { [nsstring cStringUsingEncoding:NSUTF8StringEncoding], [nsstring lengthOfBytesUsingEncoding:NSUTF8StringEncoding] };
+ NSString *original = [[NSString alloc] initWithBytesNoCopy:const_cast<char *>(string.data())
+ length:string.size()
+ encoding:NSUTF8StringEncoding
+ freeWhenDone:NO];
+ NSString *lowercase = [original lowercaseString];
+ const std::string result{[lowercase cStringUsingEncoding : NSUTF8StringEncoding],
+ [lowercase lengthOfBytesUsingEncoding:NSUTF8StringEncoding]};
+ [original release];
+ return result;
}
}
diff --git a/platform/default/application_root.cpp b/platform/default/application_root.cpp
index f25a44d46b..6669a049a4 100644
--- a/platform/default/application_root.cpp
+++ b/platform/default/application_root.cpp
@@ -1,13 +1,26 @@
#include <mbgl/platform/platform.hpp>
-#include <mbgl/util/uv.hpp>
+#include <uv.h>
+#include <libgen.h>
namespace mbgl {
namespace platform {
-// Returns the path the application root.
-std::string applicationRoot() {
- static const std::string root = uv::cwd();
+// Returns the path to the root folder of the application.
+const std::string &applicationRoot() {
+ static const std::string root = []() -> std::string {
+ size_t max = 0;
+ std::string dir;
+ do {
+ // Gradually increase the length of the string in case the path was truncated.
+ max += 256;
+ dir.resize(max);
+ uv_exepath(const_cast<char *>(dir.data()), &max);
+ } while (max == dir.size());
+ dir.resize(max - 1);
+ dir = dirname(const_cast<char *>(dir.c_str()));
+ return dir;
+ }();
return root;
}
diff --git a/platform/default/asset_request_fs.cpp b/platform/default/asset_request_fs.cpp
new file mode 100644
index 0000000000..24856e520b
--- /dev/null
+++ b/platform/default/asset_request_fs.cpp
@@ -0,0 +1,252 @@
+#include <mbgl/storage/default/asset_request.hpp>
+#include <mbgl/storage/response.hpp>
+#include <mbgl/util/std.hpp>
+#include <mbgl/util/util.hpp>
+#include <mbgl/util/uv.hpp>
+#include <mbgl/platform/platform.hpp>
+
+#include <uv.h>
+
+#pragma GCC diagnostic push
+#ifndef __clang__
+#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
+#pragma GCC diagnostic ignored "-Wshadow"
+#endif
+#include <boost/algorithm/string.hpp>
+#pragma GCC diagnostic pop
+
+#include <cassert>
+
+
+namespace algo = boost::algorithm;
+
+namespace mbgl {
+
+class AssetRequestImpl {
+ MBGL_STORE_THREAD(tid)
+
+public:
+ AssetRequestImpl(AssetRequest *request, uv_loop_t *loop);
+ ~AssetRequestImpl();
+
+ static void fileOpened(uv_fs_t *req);
+ static void fileStated(uv_fs_t *req);
+ static void fileRead(uv_fs_t *req);
+ static void fileClosed(uv_fs_t *req);
+ static void notifyError(uv_fs_t *req);
+ static void cleanup(uv_fs_t *req);
+
+
+ AssetRequest *request = nullptr;
+ bool canceled = false;
+ uv_fs_t req;
+ uv_file fd = -1;
+ uv_buf_t buffer;
+ std::unique_ptr<Response> response;
+};
+
+AssetRequestImpl::~AssetRequestImpl() {
+ MBGL_VERIFY_THREAD(tid);
+
+ if (request) {
+ request->ptr = nullptr;
+ }
+}
+
+AssetRequestImpl::AssetRequestImpl(AssetRequest *request_, uv_loop_t *loop) : request(request_) {
+ req.data = this;
+
+ const auto &url = request->resource.url;
+ std::string path;
+ if (url.size() <= 8 || url[8] == '/') {
+ // This is an empty or absolute path.
+ path = url.substr(8);
+ } else {
+ // This is a relative path. Prefix with the application root.
+ path = platform::assetRoot() + "/" + url.substr(8);
+ }
+
+ uv_fs_open(loop, &req, path.c_str(), O_RDONLY, S_IRUSR, fileOpened);
+}
+
+void AssetRequestImpl::fileOpened(uv_fs_t *req) {
+ assert(req->data);
+ auto self = reinterpret_cast<AssetRequestImpl *>(req->data);
+ MBGL_VERIFY_THREAD(self->tid);
+
+ if (req->result < 0) {
+ // Opening failed or was canceled. There isn't much left we can do.
+ notifyError(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 (self->canceled) {
+ // The request was canceled.
+ uv_fs_close(req->loop, req, fd, fileClosed);
+ } else {
+ self->fd = fd;
+ uv_fs_fstat(req->loop, req, fd, fileStated);
+ }
+ }
+}
+
+void AssetRequestImpl::fileStated(uv_fs_t *req) {
+ assert(req->data);
+ auto self = reinterpret_cast<AssetRequestImpl *>(req->data);
+ MBGL_VERIFY_THREAD(self->tid);
+
+ if (req->result != 0 || self->canceled) {
+ // Stating failed or was canceled. We already have an open file handle
+ // though, which we'll have to close.
+ notifyError(req);
+
+ uv_fs_req_cleanup(req);
+ uv_fs_close(req->loop, req, self->fd, fileClosed);
+ } else {
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+ auto stat = static_cast<const uv_statbuf_t *>(req->ptr);
+#else
+ auto stat = static_cast<const uv_stat_t *>(req->ptr);
+#endif
+ if (stat->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.
+ auto response = util::make_unique<Response>();
+ response->status = Response::Error;
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+ response->message = uv_strerror(uv_err_t {UV_EFBIG, 0});
+#else
+ response->message = uv_strerror(UV_EFBIG);
+#endif
+ assert(self->request);
+ self->request->notify(std::move(response), FileCache::Hint::No);
+ delete self->request;
+
+ uv_fs_req_cleanup(req);
+ uv_fs_close(req->loop, req, self->fd, fileClosed);
+ } else {
+ self->response = util::make_unique<Response>();
+#ifdef __APPLE__
+ self->response->modified = stat->st_mtimespec.tv_sec;
+#else
+ self->response->modified = stat->st_mtime;
+#endif
+ self->response->etag = std::to_string(stat->st_ino);
+ const auto size = (unsigned int)(stat->st_size);
+ self->response->data.resize(size);
+ self->buffer = uv_buf_init(const_cast<char *>(self->response->data.data()), size);
+ uv_fs_req_cleanup(req);
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+ uv_fs_read(req->loop, req, self->fd, self->buffer.base, self->buffer.len, -1, fileRead);
+#else
+ uv_fs_read(req->loop, req, self->fd, &self->buffer, 1, 0, fileRead);
+#endif
+ }
+ }
+}
+
+void AssetRequestImpl::fileRead(uv_fs_t *req) {
+ assert(req->data);
+ auto self = reinterpret_cast<AssetRequestImpl *>(req->data);
+ MBGL_VERIFY_THREAD(self->tid);
+
+ if (req->result < 0 || self->canceled) {
+ // Stating failed or was canceled. We already have an open file handle
+ // though, which we'll have to close.
+ notifyError(req);
+ } else {
+ // File was successfully read.
+ self->response->status = Response::Successful;
+ assert(self->request);
+ self->request->notify(std::move(self->response), FileCache::Hint::No);
+ delete self->request;
+ }
+
+ uv_fs_req_cleanup(req);
+ uv_fs_close(req->loop, req, self->fd, fileClosed);
+}
+
+void AssetRequestImpl::fileClosed(uv_fs_t *req) {
+ assert(req->data);
+ auto self = reinterpret_cast<AssetRequestImpl *>(req->data);
+ MBGL_VERIFY_THREAD(self->tid);
+ (void(self)); // Silence unused variable error in Release mode
+
+ if (req->result < 0) {
+ // Closing the file failed. But there isn't anything we can do.
+ }
+
+ cleanup(req);
+}
+
+void AssetRequestImpl::notifyError(uv_fs_t *req) {
+ assert(req->data);
+ auto self = reinterpret_cast<AssetRequestImpl *>(req->data);
+ MBGL_VERIFY_THREAD(self->tid);
+
+ if (req->result < 0 && !self->canceled && req->result != UV_ECANCELED) {
+ auto response = util::make_unique<Response>();
+ response->status = Response::Error;
+ response->message = uv::getFileRequestError(req);
+ assert(self->request);
+ self->request->notify(std::move(response), FileCache::Hint::No);
+ delete self->request;
+ }
+}
+
+void AssetRequestImpl::cleanup(uv_fs_t *req) {
+ assert(req->data);
+ auto self = reinterpret_cast<AssetRequestImpl *>(req->data);
+ MBGL_VERIFY_THREAD(self->tid);
+ uv_fs_req_cleanup(req);
+ delete self;
+}
+
+// -------------------------------------------------------------------------------------------------
+
+AssetRequest::AssetRequest(DefaultFileSource *source_, const Resource &resource_)
+ : SharedRequestBase(source_, resource_) {
+ assert(algo::starts_with(resource.url, "asset://"));
+}
+
+AssetRequest::~AssetRequest() {
+ MBGL_VERIFY_THREAD(tid);
+
+ if (ptr) {
+ reinterpret_cast<AssetRequestImpl *>(ptr)->request = nullptr;
+ }
+}
+
+void AssetRequest::start(uv_loop_t *loop, std::unique_ptr<Response> response) {
+ MBGL_VERIFY_THREAD(tid);
+
+ // We're ignoring the existing response if any.
+ (void(response));
+
+ assert(!ptr);
+ ptr = new AssetRequestImpl(this, loop);
+ // Note: the AssetRequestImpl deletes itself.
+}
+
+void AssetRequest::cancel() {
+ MBGL_VERIFY_THREAD(tid);
+
+ if (ptr) {
+ reinterpret_cast<AssetRequestImpl *>(ptr)->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. The cancelation callback will delete the AssetRequest object.
+ uv_cancel((uv_req_t *)&reinterpret_cast<AssetRequestImpl *>(ptr)->req);
+ } else {
+ // This request is canceled before we called start. We're safe to delete
+ // ourselves now.
+ delete this;
+ }
+}
+
+}
diff --git a/platform/default/asset_request_libuv.cpp b/platform/default/asset_request_libuv.cpp
deleted file mode 100644
index 0e0b7280a7..0000000000
--- a/platform/default/asset_request_libuv.cpp
+++ /dev/null
@@ -1,223 +0,0 @@
-#include <mbgl/storage/asset_request.hpp>
-#include <mbgl/storage/response.hpp>
-#include <mbgl/platform/platform.hpp>
-#include <mbgl/util/std.hpp>
-
-#include <uv.h>
-
-#include <limits>
-
-namespace mbgl {
-
-struct AssetRequestBaton {
- AssetRequestBaton(AssetRequest *request_, const std::string &path, uv_loop_t *loop);
- ~AssetRequestBaton();
-
- void cancel();
- static void fileOpened(uv_fs_t *req);
- static void fileStated(uv_fs_t *req);
- static void fileRead(uv_fs_t *req);
- static void fileClosed(uv_fs_t *req);
- static void notifyError(uv_fs_t *req);
- static void cleanup(uv_fs_t *req);
-
- const std::thread::id threadId;
- AssetRequest *request = nullptr;
- uv_fs_t req;
- uv_file fd = -1;
- bool canceled = false;
- std::string body;
- uv_buf_t buffer;
-};
-
-AssetRequestBaton::AssetRequestBaton(AssetRequest *request_, const std::string &path, uv_loop_t *loop)
- : threadId(std::this_thread::get_id()), request(request_) {
- req.data = this;
- uv_fs_open(loop, &req, path.c_str(), O_RDONLY, S_IRUSR, fileOpened);
-}
-
-AssetRequestBaton::~AssetRequestBaton() {
-}
-
-void AssetRequestBaton::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 AssetRequestBaton::notifyError(uv_fs_t *req) {
- AssetRequestBaton *ptr = reinterpret_cast<AssetRequestBaton *>(req->data);
- assert(std::this_thread::get_id() == ptr->threadId);
-
- if (ptr->request && req->result < 0 && !ptr->canceled && req->result != UV_ECANCELED) {
- ptr->request->response = util::make_unique<Response>();
- ptr->request->response->code = req->result == UV_ENOENT ? 404 : 500;
-#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
- ptr->request->response->message = uv_strerror(uv_last_error(req->loop));
-#else
- ptr->request->response->message = uv_strerror(int(req->result));
-#endif
- ptr->request->notify();
- }
-}
-
-void AssetRequestBaton::fileOpened(uv_fs_t *req) {
- AssetRequestBaton *ptr = reinterpret_cast<AssetRequestBaton *>(req->data);
- assert(std::this_thread::get_id() == ptr->threadId);
-
- if (req->result < 0) {
- // Opening failed or was canceled. There isn't much left we can do.
- notifyError(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 AssetRequest object has been destructed, or the
- // request was canceled.
- uv_fs_close(req->loop, req, fd, fileClosed);
- } else {
- ptr->fd = fd;
- uv_fs_fstat(req->loop, req, fd, fileStated);
- }
- }
-}
-
-void AssetRequestBaton::fileStated(uv_fs_t *req) {
- AssetRequestBaton *ptr = reinterpret_cast<AssetRequestBaton *>(req->data);
- assert(std::this_thread::get_id() == ptr->threadId);
-
- 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.
- notifyError(req);
-
- uv_fs_req_cleanup(req);
- uv_fs_close(req->loop, req, ptr->fd, fileClosed);
- } else {
-#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
- const uv_statbuf_t *stat = static_cast<const uv_statbuf_t *>(req->ptr);
-#else
- const uv_stat_t *stat = static_cast<const uv_stat_t *>(req->ptr);
-#endif
- if (stat->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 = util::make_unique<Response>();
- ptr->request->response->code = UV_EFBIG;
-#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
- ptr->request->response->message = uv_strerror(uv_err_t {UV_EFBIG, 0});
-#else
- ptr->request->response->message = uv_strerror(UV_EFBIG);
-#endif
- ptr->request->notify();
- }
-
- uv_fs_req_cleanup(req);
- uv_fs_close(req->loop, req, ptr->fd, fileClosed);
- } else {
- const unsigned int size = (unsigned int)(stat->st_size);
- ptr->body.resize(size);
- ptr->buffer = uv_buf_init(const_cast<char *>(ptr->body.data()), size);
- uv_fs_req_cleanup(req);
-#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
- uv_fs_read(req->loop, req, ptr->fd, ptr->buffer.base, ptr->buffer.len, -1, fileRead);
-#else
- uv_fs_read(req->loop, req, ptr->fd, &ptr->buffer, 1, 0, fileRead);
-#endif
- }
- }
-}
-
-void AssetRequestBaton::fileRead(uv_fs_t *req) {
- AssetRequestBaton *ptr = reinterpret_cast<AssetRequestBaton *>(req->data);
- assert(std::this_thread::get_id() == ptr->threadId);
-
- if (req->result < 0 || ptr->canceled || !ptr->request) {
- // Reading failed or was canceled. We already have an open file handle
- // though, which we'll have to close.
- notifyError(req);
- } else {
- // File was successfully read.
- if (ptr->request) {
- ptr->request->response = util::make_unique<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, fileClosed);
-}
-
-void AssetRequestBaton::fileClosed(uv_fs_t *req) {
- assert(std::this_thread::get_id() == (reinterpret_cast<AssetRequestBaton *>(req->data))->threadId);
-
- if (req->result < 0) {
- // Closing the file failed. But there isn't anything we can do.
- }
-
- cleanup(req);
-}
-
-void AssetRequestBaton::cleanup(uv_fs_t *req) {
- AssetRequestBaton *ptr = reinterpret_cast<AssetRequestBaton *>(req->data);
- assert(std::this_thread::get_id() == ptr->threadId);
-
- if (ptr->request) {
- ptr->request->ptr = nullptr;
- }
-
- uv_fs_req_cleanup(req);
- delete ptr;
- ptr = nullptr;
-}
-
-
-AssetRequest::AssetRequest(const std::string &path_, uv_loop_t *loop)
- : BaseRequest(path_) {
- if (!path.empty() && path[0] == '/') {
- // This is an absolute path. We don't allow this. Note that this is not a way to absolutely
- // prevent access to resources outside the application bundle; e.g. there could be symlinks
- // in the application bundle that link to outside. We don't care about these.
- response = util::make_unique<Response>();
- response->code = 403;
- response->message = "Path is outside the application bundle";
- notify();
- } else {
- // Note: The AssetRequestBaton object is deleted in AssetRequestBaton::cleanup().
- ptr = new AssetRequestBaton(this, platform::applicationRoot() + "/" + path, loop);
- }
-}
-
-void AssetRequest::cancel() {
- assert(std::this_thread::get_id() == threadId);
-
- if (ptr) {
- ptr->cancel();
-
- // When deleting a AssetRequest 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;
- ptr = nullptr;
- }
-
- notify();
-}
-
-AssetRequest::~AssetRequest() {
- assert(std::this_thread::get_id() == threadId);
- cancel();
-
- // Note: The AssetRequestBaton object is deleted in AssetRequestBaton::cleanup().
-}
-
-}
diff --git a/platform/default/asset_request_zip.cpp b/platform/default/asset_request_zip.cpp
new file mode 100644
index 0000000000..9a5f598bb6
--- /dev/null
+++ b/platform/default/asset_request_zip.cpp
@@ -0,0 +1,301 @@
+#include <mbgl/storage/default/asset_request.hpp>
+#include <mbgl/storage/default/thread_context.hpp>
+#include <mbgl/android/jni.hpp>
+#include <mbgl/storage/response.hpp>
+#include <mbgl/util/std.hpp>
+#include <mbgl/platform/platform.hpp>
+
+#include "uv_zip.h"
+
+#pragma GCC diagnostic push
+#ifndef __clang__
+#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
+#pragma GCC diagnostic ignored "-Wshadow"
+#endif
+#include <boost/algorithm/string.hpp>
+#pragma GCC diagnostic pop
+
+#include <forward_list>
+
+namespace algo = boost::algorithm;
+
+namespace mbgl {
+
+class AssetZipContext : public ThreadContext<AssetZipContext> {
+public:
+ AssetZipContext(uv_loop_t *loop);
+ ~AssetZipContext();
+
+ uv_zip_t *getHandle();
+ void returnHandle(uv_zip_t *zip);
+
+private:
+ // A list of resuable uv_zip handles to avoid creating and destroying them all the time.
+ std::forward_list<uv_zip_t *> handles;
+};
+
+// -------------------------------------------------------------------------------------------------
+
+template<> pthread_key_t ThreadContext<AssetZipContext>::key{};
+template<> pthread_once_t ThreadContext<AssetZipContext>::once = PTHREAD_ONCE_INIT;
+
+AssetZipContext::AssetZipContext(uv_loop_t *loop_) : ThreadContext(loop_) {
+}
+
+uv_zip_t *AssetZipContext::getHandle() {
+ if (!handles.empty()) {
+ auto zip = handles.front();
+ handles.pop_front();
+ return zip;
+ } else {
+ return nullptr;
+ }
+}
+
+void AssetZipContext::returnHandle(uv_zip_t *zip) {
+ uv_zip_cleanup(zip);
+ handles.push_front(zip);
+}
+
+AssetZipContext::~AssetZipContext() {
+ // Close all zip handles
+ for (auto zip : handles) {
+ uv_zip_discard(loop, zip, [](uv_zip_t *zip_) {
+ uv_zip_cleanup(zip_);
+ delete zip_;
+ });
+ }
+ handles.clear();
+}
+
+// -------------------------------------------------------------------------------------------------
+
+class AssetRequestImpl {
+ MBGL_STORE_THREAD(tid)
+
+public:
+ AssetRequestImpl(AssetRequest *request, uv_loop_t *loop);
+ ~AssetRequestImpl();
+
+ void cancel();
+
+private:
+ AssetZipContext &context;
+ AssetRequest *request = nullptr;
+ const std::string path;
+ std::unique_ptr<Response> response;
+ uv_buf_t buffer;
+
+private:
+ void openZipArchive();
+ void archiveOpened(uv_zip_t *zip);
+ void fileStated(uv_zip_t *zip);
+ void fileOpened(uv_zip_t *zip);
+ void fileRead(uv_zip_t *zip);
+ void fileClosed(uv_zip_t *zip);
+ void cleanup(uv_zip_t *zip);
+
+ void notifyError(const char *message);
+};
+
+// -------------------------------------------------------------------------------------------------
+
+AssetRequestImpl::~AssetRequestImpl() {
+ MBGL_VERIFY_THREAD(tid);
+
+ if (request) {
+ request->ptr = nullptr;
+ }
+}
+
+AssetRequestImpl::AssetRequestImpl(AssetRequest *request_, uv_loop_t *loop)
+ : context(*AssetZipContext::Get(loop)),
+ request(request_),
+ path(request->resource.url.substr(8)) {
+ auto zip = context.getHandle();
+ if (zip) {
+ archiveOpened(zip);
+ } else {
+ openZipArchive();
+ }
+}
+
+void AssetRequestImpl::openZipArchive() {
+ uv_fs_t *req = new uv_fs_t();
+ req->data = this;
+
+ // We're using uv_fs_open first to obtain a file descriptor. Then, uv_zip_fdopen will operate
+ // on a read-only file.
+ uv_fs_open(context.loop, req, platform::assetRoot().c_str(), O_RDONLY, S_IRUSR, [](uv_fs_t *fsReq) {
+ if (fsReq->result < 0) {
+ auto impl = reinterpret_cast<AssetRequestImpl *>(fsReq->data);
+ impl->notifyError(uv::getFileRequestError(fsReq));
+ delete impl;
+ } else {
+ uv_zip_t *zip = new uv_zip_t();
+ uv_zip_init(zip);
+ zip->data = fsReq->data;
+ uv_zip_fdopen(fsReq->loop, zip, uv_file(fsReq->result), 0, [](uv_zip_t *openZip) {
+ auto impl = reinterpret_cast<AssetRequestImpl *>(openZip->data);
+ if (openZip->result < 0) {
+ impl->notifyError(openZip->message);
+ delete openZip;
+ delete impl;
+ } else {
+ impl->archiveOpened(openZip);
+ }
+ });
+ }
+
+ uv_fs_req_cleanup(fsReq);
+ delete fsReq;
+ fsReq = nullptr;
+ });
+}
+
+#define INVOKE_MEMBER(name) \
+ [](uv_zip_t *zip_) { \
+ assert(zip_->data); \
+ reinterpret_cast<AssetRequestImpl *>(zip_->data)->name(zip_); \
+ }
+
+void AssetRequestImpl::archiveOpened(uv_zip_t *zip) {
+ MBGL_VERIFY_THREAD(tid);
+
+ zip->data = this;
+ uv_zip_stat(context.loop, zip, path.c_str(), 0, INVOKE_MEMBER(fileStated));
+}
+
+void AssetRequestImpl::fileStated(uv_zip_t *zip) {
+ MBGL_VERIFY_THREAD(tid);
+
+ if (!request || zip->result < 0) {
+ // Stat failed, probably because the file doesn't exist.
+ if (zip->result < 0) {
+ notifyError(zip->message);
+ }
+ cleanup(zip);
+ } else if (!(zip->stat->valid & ZIP_STAT_SIZE)) {
+ // We couldn't obtain the size of the file.
+ notifyError("Could not determine file size in zip file");
+ cleanup(zip);
+ } else {
+ response = util::make_unique<Response>();
+
+ // Allocate the space for reading the data.
+ response->data.resize(zip->stat->size);
+ buffer = uv_buf_init(const_cast<char *>(response->data.data()), zip->stat->size);
+
+ // Get the modification time in case we have one.
+ if (zip->stat->valid & ZIP_STAT_MTIME) {
+ response->modified = zip->stat->mtime;
+ }
+
+ uv_zip_fopen(context.loop, zip, path.c_str(), 0, INVOKE_MEMBER(fileOpened));
+ }
+}
+
+void AssetRequestImpl::fileOpened(uv_zip_t *zip) {
+ MBGL_VERIFY_THREAD(tid);
+
+ if (zip->result < 0) {
+ // Opening failed.
+ notifyError(zip->message);
+ cleanup(zip);
+ } else if (!request) {
+ // The request was canceled. Close the file again.
+ uv_zip_fclose(context.loop, zip, zip->file, INVOKE_MEMBER(fileClosed));
+ } else {
+ uv_zip_fread(context.loop, zip, zip->file, &buffer, INVOKE_MEMBER(fileRead));
+ }
+}
+
+void AssetRequestImpl::fileRead(uv_zip_t *zip) {
+ MBGL_VERIFY_THREAD(tid);
+
+ if (zip->result < 0) {
+ // Reading failed. We still have an open file handle though.
+ notifyError(zip->message);
+ } else if (request) {
+ response->status = Response::Successful;
+ request->notify(std::move(response), FileCache::Hint::No);
+ delete request;
+ assert(request == nullptr);
+ }
+
+ uv_zip_fclose(context.loop, zip, zip->file, INVOKE_MEMBER(fileClosed));
+}
+
+void AssetRequestImpl::fileClosed(uv_zip_t *zip) {
+ MBGL_VERIFY_THREAD(tid);
+
+ if (zip->result < 0) {
+ // Closing the file failed. But there isn't anything we can do.
+ }
+
+ cleanup(zip);
+}
+
+void AssetRequestImpl::cleanup(uv_zip_t *zip) {
+ MBGL_VERIFY_THREAD(tid);
+
+ context.returnHandle(zip);
+ delete this;
+}
+
+void AssetRequestImpl::notifyError(const char *message) {
+ MBGL_VERIFY_THREAD(tid);
+
+ if (request) {
+ response = util::make_unique<Response>();
+ response->status = Response::Error;
+ response->message = message;
+ request->notify(std::move(response), FileCache::Hint::No);
+ delete request;
+ assert(request == nullptr);
+ } else {
+ // The request was already canceled and deleted.
+ }
+}
+
+void AssetRequestImpl::cancel() {
+ request = nullptr;
+}
+
+// -------------------------------------------------------------------------------------------------
+
+AssetRequest::AssetRequest(DefaultFileSource *source_, const Resource &resource_)
+ : SharedRequestBase(source_, resource_) {
+ assert(algo::starts_with(resource.url, "asset://"));
+}
+
+AssetRequest::~AssetRequest() {
+ MBGL_VERIFY_THREAD(tid);
+
+ if (ptr) {
+ reinterpret_cast<AssetRequestImpl *>(ptr)->cancel();
+ }
+}
+
+void AssetRequest::start(uv_loop_t *loop, std::unique_ptr<Response> response) {
+ MBGL_VERIFY_THREAD(tid);
+
+ // We're ignoring the existing response if any.
+ (void(response));
+
+ assert(!ptr);
+ ptr = new AssetRequestImpl(this, loop);
+ // Note: the AssetRequestImpl deletes itself.
+}
+
+void AssetRequest::cancel() {
+ MBGL_VERIFY_THREAD(tid);
+
+ if (ptr) {
+ reinterpret_cast<AssetRequestImpl *>(ptr)->cancel();
+ }
+
+ delete this;
+}
+
+}
diff --git a/platform/default/asset_root.cpp b/platform/default/asset_root.cpp
new file mode 100644
index 0000000000..2b20018c40
--- /dev/null
+++ b/platform/default/asset_root.cpp
@@ -0,0 +1,28 @@
+#include <mbgl/platform/platform.hpp>
+
+#include <uv.h>
+#include <libgen.h>
+
+namespace mbgl {
+namespace platform {
+
+// Returns the path to the root folder of the application.
+const std::string &assetRoot() {
+ static const std::string root = []() -> std::string {
+ size_t max = 0;
+ std::string dir;
+ do {
+ // Gradually increase the length of the string in case the path was truncated.
+ max += 256;
+ dir.resize(max);
+ uv_exepath(const_cast<char *>(dir.data()), &max);
+ } while (max == dir.size());
+ dir.resize(max - 1);
+ dir = dirname(const_cast<char *>(dir.c_str()));
+ return dir;
+ }();
+ return root;
+}
+
+}
+}
diff --git a/platform/default/cache_database_tmp.cpp b/platform/default/cache_database_tmp.cpp
deleted file mode 100644
index 6132ace692..0000000000
--- a/platform/default/cache_database_tmp.cpp
+++ /dev/null
@@ -1,12 +0,0 @@
-#include <mbgl/platform/platform.hpp>
-
-namespace mbgl {
-namespace platform {
-
-// Returns the path to the default cache database on this system.
-std::string defaultCacheDatabase() {
- return "/tmp/mbgl-cache.db";
-}
-
-}
-}
diff --git a/src/mbgl/util/compression.cpp b/platform/default/compression.cpp
index d6d6370546..c8b38e742f 100644
--- a/src/mbgl/util/compression.cpp
+++ b/platform/default/compression.cpp
@@ -1,10 +1,25 @@
-#include <mbgl/util/compression.hpp>
+#include "compression.hpp"
#include <zlib.h>
#include <cstring>
#include <stdexcept>
+
+// Check zlib library version.
+const static bool zlibVersionCheck = []() {
+ const char *const version = zlibVersion();
+ if (version[0] != ZLIB_VERSION[0]) {
+ char message[96];
+ snprintf(message, 96, "zlib version mismatch: headers report %s, but library reports %s",
+ ZLIB_VERSION, version);
+ throw std::runtime_error(message);
+ }
+
+ return true;
+}();
+
+
namespace mbgl {
namespace util {
diff --git a/src/mbgl/util/compression.hpp b/platform/default/compression.hpp
index a33b2476a7..a33b2476a7 100644
--- a/src/mbgl/util/compression.hpp
+++ b/platform/default/compression.hpp
diff --git a/platform/default/http_request_baton_curl.cpp b/platform/default/http_request_baton_curl.cpp
deleted file mode 100644
index 9e8cf64716..0000000000
--- a/platform/default/http_request_baton_curl.cpp
+++ /dev/null
@@ -1,629 +0,0 @@
-#include <mbgl/mbgl.hpp>
-#include <mbgl/storage/http_request_baton.hpp>
-#include <mbgl/util/uv-messenger.h>
-#include <mbgl/util/time.hpp>
-#include <mbgl/util/string.hpp>
-#include <mbgl/util/std.hpp>
-#include <mbgl/util/version.hpp>
-
-#ifdef __ANDROID__
- #include <mbgl/android/jni.hpp>
- #include <zip.h>
- #include <openssl/ssl.h>
-#endif
-
-#include <uv.h>
-#include <curl/curl.h>
-
-#include <sys/utsname.h>
-
-#include <queue>
-#include <cassert>
-#include <cstring>
-#include <thread>
-
-
-// Check curl library version.
-const static bool curl_version_check = []() {
- const auto version = curl_version_info(CURLVERSION_NOW);
- if (version->version_num != LIBCURL_VERSION_NUM) {
- throw std::runtime_error(mbgl::util::sprintf<96>(
- "libcurl version mismatch: headers report %d.%d.%d, but library reports %d.%d.%d",
- (LIBCURL_VERSION_NUM >> 16) & 0xFF, (LIBCURL_VERSION_NUM >> 8) & 0xFF, LIBCURL_VERSION_NUM & 0xFF,
- (version->version_num >> 16) & 0xFF, (version->version_num >> 8) & 0xFF, version->version_num & 0xFF));
- }
- return true;
-}();
-
-
-// This file contains code from http://curl.haxx.se/libcurl/c/multi-uv.html:
-
-/***************************************************************************
- * _ _ ____ _
- * 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.
- *
- ***************************************************************************/
-
-/* Example application code using the multi socket interface to download
- multiple files at once, but instead of using curl_multi_perform and
- curl_multi_wait, which uses select(), we use libuv.
- It supports epoll, kqueue, etc. on unixes and fast IO completion ports on
- Windows, which means, it should be very fast on all platforms..
-
- Written by Clemens Gruber, based on an outdated example from uvbook and
- some tests from libuv.
-
- Requires libuv and (of course) libcurl.
-
- See http://nikhilm.github.com/uvbook/ for more information on libuv.
-*/
-
-// Handles the request thread + messaging to the thread.
-static uv_once_t once;
-static uv_loop_t *loop = nullptr;
-static uv_messenger_t start_messenger;
-static uv_messenger_t stop_messenger;
-static uv_thread_t thread;
-static std::thread::id thread_id;
-
-// Used as the CURL timer function to periodically check for socket updates.
-static uv_timer_t timeout;
-
-// CURL multi handle that we use to request multiple URLs at the same time, without having to block
-// and spawn threads.
-static CURLM *multi = nullptr;
-
-// CURL share handles are used for sharing session state (e.g.)
-static uv_mutex_t share_mutex;
-static CURLSH *share = nullptr;
-
-// A queue that we use for storing resuable CURL easy handles to avoid creating and destroying them
-// all the time.
-static std::queue<CURL *> handles;
-
-namespace mbgl {
-
-struct Context {
- const util::ptr<HTTPRequestBaton> baton;
- CURL *handle = nullptr;
- curl_slist *headers = nullptr;
-
- Context(const util::ptr<HTTPRequestBaton> &baton_) : baton(baton_) {
- assert(baton);
- baton->ptr = this;
-
- if (!handles.empty()) {
- handle = handles.front();
- handles.pop();
- } else {
- handle = curl_easy_init();
- }
- }
-
- ~Context() {
- baton->ptr = nullptr;
-
- if (headers) {
- curl_slist_free_all(headers);
- headers = nullptr;
- }
-
- CURLMcode error = curl_multi_remove_handle(multi, handle);
- if (error != CURLM_OK) {
- baton->response = util::make_unique<Response>();
- baton->response->code = -1;
- baton->response->message = curl_multi_strerror(error);
- }
-
- curl_easy_setopt(handle, CURLOPT_PRIVATE, nullptr);
- curl_easy_reset(handle);
- handles.push(handle);
- handle = nullptr;
-
- if (baton->async) {
- uv_async_send(baton->async);
- baton->async = nullptr;
- }
- }
-};
-
-struct Socket {
-private:
- uv_poll_t poll_handle;
-
-public:
- const curl_socket_t sockfd = 0;
-
-public:
- Socket(curl_socket_t sockfd_) : sockfd(sockfd_) {
- uv_poll_init_socket(loop, &poll_handle, sockfd);
- poll_handle.data = this;
- }
-
- void start(int events, uv_poll_cb cb) {
- uv_poll_start(&poll_handle, events, cb);
- }
-
- void stop() {
- assert(poll_handle.data);
- poll_handle.data = nullptr;
- uv_poll_stop(&poll_handle);
- uv_close((uv_handle_t *)&poll_handle, [](uv_handle_t *handle) {
- delete (Socket *)handle->data;
- });
- }
-
-private:
- // Make the destructor private to ensure that we can only close the Socket
- // with stop(), and disallow manual deletion.
- ~Socket() {
- assert(!poll_handle.data);
- }
-};
-
-// Locks the CURL share handle
-void curl_share_lock(CURL *, curl_lock_data, curl_lock_access, void *) {
- uv_mutex_lock(&share_mutex);
-}
-
-// Unlocks the CURL share handle
-void curl_share_unlock(CURL *, curl_lock_data, void *) {
- uv_mutex_unlock(&share_mutex);
-}
-
-void check_multi_info() {
- CURLMsg *message = nullptr;
- int pending = 0;
-
- while ((message = curl_multi_info_read(multi, &pending))) {
- switch (message->msg) {
- case CURLMSG_DONE: {
- Context *context = nullptr;
- curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&context);
- assert(context);
-
- auto baton = context->baton;
-
- // This request is complete. We are removing the pointer to the CURL easy handle again
- // to prevent this request from getting canceled.
- context->baton->ptr = nullptr;
-
- // Add human-readable error code
- if (message->data.result != CURLE_OK) {
- baton->response->message = curl_easy_strerror(message->data.result);
- baton->response->code = -1;
-
- switch (message->data.result) {
- case CURLE_COULDNT_RESOLVE_PROXY:
- case CURLE_COULDNT_RESOLVE_HOST:
- case CURLE_COULDNT_CONNECT:
- baton->type = HTTPResponseType::ConnectionError;
- break;
-
- case CURLE_OPERATION_TIMEDOUT:
- baton->type = HTTPResponseType::TemporaryError;
- break;
-
- default:
- baton->type = HTTPResponseType::PermanentError;
- }
- } else {
- long code = 0;
- curl_easy_getinfo(message->easy_handle, CURLINFO_RESPONSE_CODE, &code);
-
- if (code != 304) {
- baton->response->code = code;
- }
-
- if (code == 304) {
- baton->type = HTTPResponseType::NotModified;
- } else if (code == 200) {
- baton->type = HTTPResponseType::Successful;
- } else if (code >= 500 && code < 600) {
- baton->type = HTTPResponseType::TemporaryError;
- } else if (code >= 400 && code < 500) {
- baton->type = HTTPResponseType::PermanentError;
- } else {
- assert(!"code must be either 200 or 304");
- }
- }
-
- delete context;
- } break;
-
- default:
- // This should never happen, because there are no other message types.
- throw std::runtime_error("CURLMSG returned unknown message type");
- }
- }
-}
-
-void curl_perform(uv_poll_t *req, int /* status */, int events) {
- int flags = 0;
-
- uv_timer_stop(&timeout);
-
- if (events & UV_READABLE) {
- flags |= CURL_CSELECT_IN;
- }
- if (events & UV_WRITABLE) {
- flags |= CURL_CSELECT_OUT;
- }
-
- Socket *context = (Socket *)req->data;
- int running_handles = 0;
- curl_multi_socket_action(multi, context->sockfd, flags, &running_handles);
-
- check_multi_info();
-}
-
-int handle_socket(CURL * /* handle */, curl_socket_t s, int action, void * /* userp */, void *socketp) {
- Socket *socket = (Socket *)socketp;
- if (!socket && action != CURL_POLL_REMOVE) {
- socket = new Socket(s);
- curl_multi_assign(multi, s, (void *)socket);
- }
-
- switch (action) {
- case CURL_POLL_IN:
- socket->start(UV_READABLE, curl_perform);
- break;
- case CURL_POLL_OUT:
- socket->start(UV_WRITABLE, curl_perform);
- break;
- case CURL_POLL_REMOVE:
- if (socket) {
- socket->stop();
- curl_multi_assign(multi, s, NULL);
- }
- break;
- default:
- abort();
- }
-
- return 0;
-}
-
-#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
-void on_timeout(uv_timer_t * /* req */, int /* status */) {
-#else
-void on_timeout(uv_timer_t * /* req */) {
-#endif
- int running_handles;
- CURLMcode error = curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0, &running_handles);
- if (error != CURLM_OK) {
- throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(error));
- }
-
- check_multi_info();
-}
-
-void start_timeout(CURLM * /* multi */, long timeout_ms, void * /* userp */) {
- if (timeout_ms <= 0) {
- timeout_ms = 1; /* 0 means directly call socket_action, but we'll do it in a bit */
- }
- uv_timer_start(&timeout, on_timeout, timeout_ms, 0);
-}
-
-void thread_init(void *) {
-#ifdef __APPLE__
- pthread_setname_np("CURL");
-#endif
- thread_id = std::this_thread::get_id();
-
- if (curl_global_init(CURL_GLOBAL_ALL)) {
- throw std::runtime_error("Could not init cURL");
- }
-
- uv_timer_init(loop, &timeout);
-
- CURLSHcode share_error;
- share = curl_share_init();
-
- share_error = curl_share_setopt(share, CURLSHOPT_LOCKFUNC, curl_share_lock);
- if (share_error != CURLSHE_OK) {
- throw std::runtime_error(std::string("CURL share error: ") + curl_share_strerror(share_error));
- }
-
- share_error = curl_share_setopt(share, CURLSHOPT_UNLOCKFUNC, curl_share_unlock);
- if (share_error != CURLSHE_OK) {
- throw std::runtime_error(std::string("CURL share error: ") + curl_share_strerror(share_error));
- }
-
- CURLMcode multi_error;
- multi = curl_multi_init();
-
- multi_error = curl_multi_setopt(multi, CURLMOPT_SOCKETFUNCTION, handle_socket);
- if (multi_error != CURLM_OK) {
- throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(multi_error));
- }
- multi_error = curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, start_timeout);
- if (multi_error != CURLM_OK) {
- throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(multi_error));
- }
-
- // Main event loop. This will not return until the request loop is terminated.
- uv_run(loop, UV_RUN_DEFAULT);
-
- curl_multi_cleanup(multi);
- multi = nullptr;
-
- curl_share_cleanup(share);
- share = nullptr;
-
- thread_id = std::thread::id();
-}
-
-// This function is called when we have new data for a request. We just append it to the string
-// containing the previous data.
-size_t curl_write_cb(void *const contents, const size_t size, const size_t nmemb, void *const userp) {
- auto &response = *(std::unique_ptr<Response> *)userp;
- assert(response);
- response->data.append((char *)contents, size * nmemb);
- return size * nmemb;
-}
-
-// Compares the beginning of the (non-zero-terminated!) data buffer with the (zero-terminated!)
-// header string. If the data buffer contains the header string at the beginning, it returns
-// the length of the header string == begin of the value, otherwise it returns npos.
-// The comparison of the header is ASCII-case-insensitive.
-size_t header_matches(const char *const header, const char *const buffer, const size_t length) {
- const size_t header_length = strlen(header);
- if (length < header_length) return std::string::npos;
- size_t i = 0;
- while (i < length && i < header_length && std::tolower(buffer[i]) == header[i]) {
- i++;
- }
- return i == header_length ? i : std::string::npos;
-}
-
-size_t curl_header_cb(char * const buffer, const size_t size, const size_t nmemb, void *const userp) {
- const size_t length = size * nmemb;
-
- auto &response = *(std::unique_ptr<Response> *)userp;
- assert(response);
-
- size_t begin = std::string::npos;
- if ((begin = header_matches("last-modified: ", buffer, length)) != std::string::npos) {
- // Always overwrite the modification date; We might already have a value here from the
- // Date header, but this one is more accurate.
- const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n
- response->modified = curl_getdate(value.c_str(), nullptr);
- } else if ((begin = header_matches("etag: ", buffer, length)) != std::string::npos) {
- response->etag = { buffer + begin, length - begin - 2 }; // remove \r\n
- } else if ((begin = header_matches("cache-control: ", buffer, length)) != std::string::npos) {
- const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n
- response->expires = Response::parseCacheControl(value.c_str());
- }
-
- return length;
-}
-
-// This function is called to load the CA bundle
-// from http://curl.haxx.se/libcurl/c/cacertinmem.html
-#ifdef __ANDROID__
-static CURLcode sslctx_function(CURL */*curl*/, void *sslctx, void */*parm*/) {
-
- int error = 0;
- struct zip *apk = zip_open(mbgl::android::apkPath.c_str(), 0, &error);
- if (apk == nullptr) {
- return CURLE_SSL_CACERT_BADFILE;
- }
-
- struct zip_file *apkFile = zip_fopen(apk, "assets/ca-bundle.crt", ZIP_FL_NOCASE);
- if (apkFile == nullptr) {
- zip_close(apk);
- apk = nullptr;
- return CURLE_SSL_CACERT_BADFILE;
- }
-
- struct zip_stat stat;
- if (zip_stat(apk, "assets/ca-bundle.crt", ZIP_FL_NOCASE, &stat) != 0) {
- zip_fclose(apkFile);
- apkFile = nullptr;
- zip_close(apk);
- apk = nullptr;
- return CURLE_SSL_CACERT_BADFILE;
- }
-
- if (stat.size > std::numeric_limits<int>::max()) {
- zip_fclose(apkFile);
- apkFile = nullptr;
- zip_close(apk);
- apk = nullptr;
- return CURLE_SSL_CACERT_BADFILE;
- }
-
- const std::unique_ptr<char[]> pem = util::make_unique<char[]>(stat.size);
-
- if (static_cast<zip_uint64_t>(zip_fread(apkFile, reinterpret_cast<void *>(pem.get()), stat.size)) != stat.size) {
- zip_fclose(apkFile);
- apkFile = nullptr;
- zip_close(apk);
- apk = nullptr;
- return CURLE_SSL_CACERT_BADFILE;
- }
-
- // get a pointer to the X509 certificate store (which may be empty!)
- X509_STORE *store = SSL_CTX_get_cert_store((SSL_CTX *)sslctx);
- if (store == nullptr) {
- return CURLE_SSL_CACERT_BADFILE;
- }
-
- // get a BIO
- BIO *bio = BIO_new_mem_buf(pem.get(), static_cast<int>(stat.size));
- if (bio == nullptr) {
- store = nullptr;
- return CURLE_SSL_CACERT_BADFILE;
- }
-
- // use it to read the PEM formatted certificate from memory into an X509
- // structure that SSL can use
- X509 *cert = nullptr;
- while (PEM_read_bio_X509(bio, &cert, 0, nullptr) != nullptr) {
- if (cert == nullptr) {
- BIO_free(bio);
- bio = nullptr;
- store = nullptr;
- return CURLE_SSL_CACERT_BADFILE;
- }
-
- // add our certificate to this store
- if (X509_STORE_add_cert(store, cert) == 0) {
- X509_free(cert);
- cert = nullptr;
- BIO_free(bio);
- bio = nullptr;
- store = nullptr;
- return CURLE_SSL_CACERT_BADFILE;
- }
-
- X509_free(cert);
- cert = nullptr;
- }
-
- // decrease reference counts
- BIO_free(bio);
- bio = nullptr;
-
- zip_fclose(apkFile);
- apkFile = nullptr;
- zip_close(apk);
- apk = nullptr;
-
- // all set to go
- return CURLE_OK;
-}
-#endif
-
-std::string buildUserAgentString() {
-#ifdef __ANDROID__
- return util::sprintf<128>("MapboxGL/%d.%d.%d (+https://mapbox.com/mapbox-gl/; %s; %s %s)",
- version::major, version::minor, version::patch, version::revision, "Android", mbgl::android::androidRelease.c_str());
-#else
- utsname name;
- uname(&name);
- return util::sprintf<128>("MapboxGL/%d.%d.%d (+https://mapbox.com/mapbox-gl/; %s; %s %s)",
- version::major, version::minor, version::patch, version::revision, name.sysname, name.release);
-#endif
-}
-
-// This function must run in the CURL thread.
-void start_request(void *const ptr) {
- assert(std::this_thread::get_id() == thread_id);
- static const std::string userAgent = buildUserAgentString();
-
- // The Context object stores information that we need to retain throughout the request, such
- // as the actual CURL easy handle, the baton, and the list of headers. The Context itself is
- // stored in both the CURL easy handle's PRIVATE field, and the baton's `ptr` field.
- auto context = new Context(*(util::ptr<HTTPRequestBaton> *)ptr);
- delete (util::ptr<HTTPRequestBaton> *)ptr;
-
- if (context->baton->response) {
- if (!context->baton->response->etag.empty()) {
- const std::string header = std::string("If-None-Match: ") + context->baton->response->etag;
- context->headers = curl_slist_append(context->headers, header.c_str());
- } else if (context->baton->response->modified) {
- const std::string time =
- std::string("If-Modified-Since: ") + util::rfc1123(context->baton->response->modified);
- context->headers = curl_slist_append(context->headers, time.c_str());
- }
- }
-
- if (context->headers) {
- curl_easy_setopt(context->handle, CURLOPT_HTTPHEADER, context->headers);
- }
-
- if (!context->baton->response) {
- context->baton->response = util::make_unique<Response>();
- }
-
- // Carry on the shared pointer in the private information of the CURL handle.
- curl_easy_setopt(context->handle, CURLOPT_PRIVATE, context);
-#ifndef __ANDROID__
- curl_easy_setopt(context->handle, CURLOPT_CAINFO, "ca-bundle.crt");
-#else
- curl_easy_setopt(context->handle, CURLOPT_SSLCERTTYPE, "PEM");
- curl_easy_setopt(context->handle, CURLOPT_SSL_CTX_FUNCTION, sslctx_function);
-#endif
- curl_easy_setopt(context->handle, CURLOPT_FOLLOWLOCATION, 1);
- curl_easy_setopt(context->handle, CURLOPT_URL, context->baton->path.c_str());
- curl_easy_setopt(context->handle, CURLOPT_WRITEFUNCTION, curl_write_cb);
- curl_easy_setopt(context->handle, CURLOPT_WRITEDATA, &context->baton->response);
- curl_easy_setopt(context->handle, CURLOPT_HEADERFUNCTION, curl_header_cb);
- curl_easy_setopt(context->handle, CURLOPT_HEADERDATA, &context->baton->response);
- curl_easy_setopt(context->handle, CURLOPT_ACCEPT_ENCODING, "gzip, deflate");
- curl_easy_setopt(context->handle, CURLOPT_USERAGENT, userAgent.c_str());
- curl_easy_setopt(context->handle, CURLOPT_SHARE, share);
-
- // Start requesting the information.
- curl_multi_add_handle(multi, context->handle);
-}
-
-// This function must run in the CURL thread.
-void stop_request(void *const ptr) {
- assert(std::this_thread::get_id() == thread_id);
- auto baton = *(util::ptr<HTTPRequestBaton> *)ptr;
- delete (util::ptr<HTTPRequestBaton> *)ptr;
- assert(baton);
-
- if (baton->async) {
- baton->type = HTTPResponseType::Canceled;
-
- assert(baton->ptr);
-
- // We can still stop the request because it is still in progress.
- delete (Context *)baton->ptr;
- assert(!baton->ptr);
- } else {
- // If the async handle is gone, it means that the actual request has been completed before
- // we got a chance to cancel it. In this case, this is a no-op. It is likely that
- // the pointer below is the last lifeline of the HTTPRequestBaton. This means we're going
- // to delete the HTTPRequestBaton in the current (CURL) thread.
- }
-}
-
-void create_thread() {
- uv_mutex_init(&share_mutex);
-#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
- loop = uv_loop_new();
-#else
- loop = new uv_loop_t;
- uv_loop_init(loop);
-#endif
- uv_messenger_init(loop, &start_messenger, start_request);
- uv_messenger_init(loop, &stop_messenger, stop_request);
- uv_thread_create(&thread, thread_init, nullptr);
-}
-
-// This function must be run from the main thread (== where the HTTPRequestBaton was created)
-void HTTPRequestBaton::start(const util::ptr<HTTPRequestBaton> &ptr) {
- assert(std::this_thread::get_id() == ptr->threadId);
- uv_once(&once, create_thread);
- uv_messenger_send(&start_messenger, new util::ptr<HTTPRequestBaton>(ptr));
-}
-
-// This function must be run from the main thread (== where the HTTPRequestBaton was created)
-void HTTPRequestBaton::stop(const util::ptr<HTTPRequestBaton> &ptr) {
- assert(std::this_thread::get_id() == ptr->threadId);
- uv_once(&once, create_thread);
- uv_messenger_send(&stop_messenger, new util::ptr<HTTPRequestBaton>(ptr));
-}
-
-}
diff --git a/platform/default/http_request_curl.cpp b/platform/default/http_request_curl.cpp
new file mode 100644
index 0000000000..411c5ee470
--- /dev/null
+++ b/platform/default/http_request_curl.cpp
@@ -0,0 +1,648 @@
+#include <mbgl/storage/default/http_request.hpp>
+#include <mbgl/storage/default/http_context.hpp>
+#include <mbgl/storage/response.hpp>
+
+#include <mbgl/util/time.hpp>
+
+#include <curl/curl.h>
+
+#include <queue>
+#include <map>
+#include <cassert>
+#include <cstring>
+
+void handleError(CURLMcode code) {
+ if (code != CURLM_OK) {
+ throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(code));
+ }
+}
+
+void handleError(CURLcode code) {
+ if (code != CURLE_OK) {
+ throw std::runtime_error(std::string("CURL easy error: ") + curl_easy_strerror(code));
+ }
+}
+
+namespace mbgl {
+
+enum class ResponseStatus : int8_t {
+ // This error probably won't be resolved by retrying anytime soon. We are giving up.
+ PermanentError,
+
+ // This error might be resolved by waiting some time (e.g. server issues).
+ // We are going to do an exponential back-off and will try again in a few seconds.
+ TemporaryError,
+
+ // This error might be resolved once the network reachability status changes.
+ // We are going to watch the network status for changes and will retry as soon as the
+ // operating system notifies us of a network status change.
+ ConnectionError,
+
+ // The request returned data successfully. We retrieved and decoded the data successfully.
+ Successful,
+
+ // The request confirmed that the data wasn't changed. We already have the data.
+ NotModified,
+};
+
+class HTTPRequestImpl;
+
+class HTTPCURLContext : public HTTPContext<HTTPCURLContext> {
+
+public:
+ HTTPCURLContext(uv_loop_t *loop);
+ ~HTTPCURLContext();
+
+ static int handleSocket(CURL *handle, curl_socket_t s, int action, void *userp, void *socketp);
+ static void perform(uv_poll_t *req, int status, int events);
+ static int startTimeout(CURLM *multi, long timeout_ms, void *userp);
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+ static void onTimeout(uv_timer_t *req, int status);
+#else
+ static void onTimeout(uv_timer_t *req);
+#endif
+
+ CURL *getHandle();
+ void returnHandle(CURL *handle);
+ void checkMultiInfo();
+
+public:
+ // Used as the CURL timer function to periodically check for socket updates.
+ uv_timer_t *timeout = nullptr;
+
+ // CURL multi handle that we use to request multiple URLs at the same time, without having to
+ // block and spawn threads.
+ CURLM *multi = nullptr;
+
+ // CURL share handles are used for sharing session state (e.g.)
+ CURLSH *share = nullptr;
+
+ // A queue that we use for storing resuable CURL easy handles to avoid creating and destroying
+ // them all the time.
+ std::queue<CURL *> handles;
+};
+
+
+class HTTPRequestImpl {
+ MBGL_STORE_THREAD(tid)
+
+public:
+ HTTPRequestImpl(HTTPRequest *request, uv_loop_t *loop, std::unique_ptr<Response> response);
+ ~HTTPRequestImpl();
+
+ void handleResult(CURLcode code);
+ void abandon();
+ void retryImmediately();
+
+private:
+ static size_t headerCallback(char *const buffer, const size_t size, const size_t nmemb, void *userp);
+ static size_t writeCallback(void *const contents, const size_t size, const size_t nmemb, void *userp);
+
+ void retry(uint64_t timeout);
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+ static void restart(uv_timer_t *timer, int);
+#else
+ static void restart(uv_timer_t *timer);
+#endif
+ void finish(ResponseStatus status);
+ void start();
+
+private:
+ HTTPCURLContext *context = nullptr;
+ HTTPRequest *request = nullptr;
+
+ // Will store the current response.
+ std::unique_ptr<Response> response;
+
+ // In case of revalidation requests, this will store the old response.
+ std::unique_ptr<Response> existingResponse;
+
+ CURL *handle = nullptr;
+ curl_slist *headers = nullptr;
+
+ uv_timer_t *timer = nullptr;
+ enum : bool { PreemptImmediately, ExponentialBackoff } strategy = PreemptImmediately;
+ int attempts = 0;
+
+ static const int maxAttempts = 4;
+};
+
+
+
+struct Socket {
+private:
+ uv_poll_t poll;
+
+public:
+ HTTPCURLContext *context = nullptr;
+ const curl_socket_t sockfd = 0;
+
+public:
+ Socket(HTTPCURLContext *context_, curl_socket_t sockfd_) : context(context_), sockfd(sockfd_) {
+ assert(context);
+ uv_poll_init_socket(context->loop, &poll, sockfd);
+ poll.data = this;
+ }
+
+ void start(int events, uv_poll_cb cb) {
+ uv_poll_start(&poll, events, cb);
+ }
+
+ void stop() {
+ assert(poll.data);
+ uv_poll_stop(&poll);
+ uv_close((uv_handle_t *)&poll, [](uv_handle_t *handle) {
+ assert(handle->data);
+ delete reinterpret_cast<Socket *>(handle->data);
+ });
+ }
+
+private:
+ // Make the destructor private to ensure that we can only close the Socket
+ // with stop(), and disallow manual deletion.
+ ~Socket() = default;
+};
+
+// -------------------------------------------------------------------------------------------------
+
+template<> pthread_key_t ThreadContext<HTTPCURLContext>::key{};
+template<> pthread_once_t ThreadContext<HTTPCURLContext>::once = PTHREAD_ONCE_INIT;
+
+HTTPCURLContext::HTTPCURLContext(uv_loop_t *loop_) : HTTPContext(loop_) {
+ if (curl_global_init(CURL_GLOBAL_ALL)) {
+ throw std::runtime_error("Could not init cURL");
+ }
+
+ timeout = new uv_timer_t;
+ timeout->data = this;
+ uv_timer_init(loop, timeout);
+
+ 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));
+}
+
+HTTPCURLContext::~HTTPCURLContext() {
+ curl_multi_cleanup(multi);
+ multi = nullptr;
+
+ curl_share_cleanup(share);
+ share = nullptr;
+
+ uv_timer_stop(timeout);
+ uv::close(timeout);
+}
+
+CURL *HTTPCURLContext::getHandle() {
+ if (!handles.empty()) {
+ auto handle = handles.front();
+ handles.pop();
+ return handle;
+ } else {
+ return curl_easy_init();
+ }
+}
+
+void HTTPCURLContext::returnHandle(CURL *handle) {
+ curl_easy_reset(handle);
+ handles.push(handle);
+}
+
+void HTTPCURLContext::checkMultiInfo() {
+ MBGL_VERIFY_THREAD(tid);
+ CURLMsg *message = nullptr;
+ int pending = 0;
+
+ while ((message = curl_multi_info_read(multi, &pending))) {
+ switch (message->msg) {
+ case CURLMSG_DONE: {
+ HTTPRequestImpl *baton = nullptr;
+ curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&baton);
+ assert(baton);
+ baton->handleResult(message->data.result);
+ } break;
+
+ default:
+ // This should never happen, because there are no other message types.
+ throw std::runtime_error("CURLMsg returned unknown message type");
+ }
+ }
+}
+
+void HTTPCURLContext::perform(uv_poll_t *req, int /* status */, int events) {
+ assert(req->data);
+ auto socket = reinterpret_cast<Socket *>(req->data);
+ auto context = socket->context;
+ MBGL_VERIFY_THREAD(context->tid);
+
+ int flags = 0;
+
+ if (events & UV_READABLE) {
+ flags |= CURL_CSELECT_IN;
+ }
+ if (events & UV_WRITABLE) {
+ flags |= CURL_CSELECT_OUT;
+ }
+
+
+ int running_handles = 0;
+ curl_multi_socket_action(context->multi, socket->sockfd, flags, &running_handles);
+ context->checkMultiInfo();
+}
+
+int HTTPCURLContext::handleSocket(CURL * /* handle */, curl_socket_t s, int action, void *userp,
+ void *socketp) {
+ auto socket = reinterpret_cast<Socket *>(socketp);
+ assert(userp);
+ auto context = reinterpret_cast<HTTPCURLContext *>(userp);
+ MBGL_VERIFY_THREAD(context->tid);
+
+ if (!socket && action != CURL_POLL_REMOVE) {
+ socket = new Socket(context, s);
+ curl_multi_assign(context->multi, s, (void *)socket);
+ }
+
+ switch (action) {
+ case CURL_POLL_IN:
+ socket->start(UV_READABLE, perform);
+ break;
+ case CURL_POLL_OUT:
+ socket->start(UV_WRITABLE, perform);
+ break;
+ case CURL_POLL_REMOVE:
+ if (socket) {
+ socket->stop();
+ curl_multi_assign(context->multi, s, nullptr);
+ }
+ break;
+ default:
+ throw std::runtime_error("Unhandled CURL socket action");
+ }
+
+ return 0;
+}
+
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+void HTTPCURLContext::onTimeout(uv_timer_t *req, int /* status */) {
+#else
+void HTTPCURLContext::onTimeout(uv_timer_t *req) {
+#endif
+ assert(req->data);
+ auto context = reinterpret_cast<HTTPCURLContext *>(req->data);
+ MBGL_VERIFY_THREAD(context->tid);
+ int 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));
+ }
+ context->checkMultiInfo();
+}
+
+int HTTPCURLContext::startTimeout(CURLM * /* multi */, long timeout_ms, void *userp) {
+ assert(userp);
+ auto context = reinterpret_cast<HTTPCURLContext *>(userp);
+ MBGL_VERIFY_THREAD(context->tid);
+ if (timeout_ms < 0) {
+ // A timeout of 0 ms means that the timer will invoked in the next loop iteration.
+ timeout_ms = 0;
+ }
+ uv_timer_stop(context->timeout);
+ uv_timer_start(context->timeout, onTimeout, timeout_ms, 0);
+ return 0;
+}
+
+// -------------------------------------------------------------------------------------------------
+
+HTTPRequestImpl::HTTPRequestImpl(HTTPRequest *request_, uv_loop_t *loop, std::unique_ptr<Response> response_)
+ : context(HTTPCURLContext::Get(loop)),
+ request(request_),
+ existingResponse(std::move(response_)),
+ handle(context->getHandle()) {
+ assert(request);
+ context->addRequest(request);
+
+ // If there's already a response, set the correct etags/modified headers to make sure we are
+ // getting a 304 response if possible. This avoids redownloading unchanged data.
+ if (existingResponse) {
+ if (!existingResponse->etag.empty()) {
+ const std::string header = std::string("If-None-Match: ") + existingResponse->etag;
+ headers = curl_slist_append(headers, header.c_str());
+ } else if (existingResponse->modified) {
+ const std::string time =
+ std::string("If-Modified-Since: ") + util::rfc1123(existingResponse->modified);
+ headers = curl_slist_append(headers, time.c_str());
+ }
+ }
+
+ if (headers) {
+ curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+ }
+
+ handleError(curl_easy_setopt(handle, CURLOPT_PRIVATE, this));
+ 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, request->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_ACCEPT_ENCODING, "gzip, deflate"));
+ handleError(curl_easy_setopt(handle, CURLOPT_USERAGENT, "MapboxGL/1.0"));
+ handleError(curl_easy_setopt(handle, CURLOPT_SHARE, context->share));
+
+ start();
+}
+
+void HTTPRequestImpl::abandon() {
+ if (request) {
+ context->removeRequest(request);
+ request = nullptr;
+ }
+}
+
+void HTTPRequestImpl::start() {
+ // Count up the attempts.
+ attempts++;
+
+ // Start requesting the information.
+ handleError(curl_multi_add_handle(context->multi, handle));
+}
+
+HTTPRequestImpl::~HTTPRequestImpl() {
+ MBGL_VERIFY_THREAD(tid);
+
+ if (request) {
+ context->removeRequest(request);
+ request->ptr = nullptr;
+ }
+
+ handleError(curl_multi_remove_handle(context->multi, handle));
+ context->returnHandle(handle);
+ handle = nullptr;
+
+ if (timer) {
+ // Stop the backoff timer to avoid re-triggering this request.
+ uv_timer_stop(timer);
+ uv::close(timer);
+ timer = nullptr;
+ }
+
+ if (headers) {
+ curl_slist_free_all(headers);
+ headers = nullptr;
+ }
+}
+
+// This function is called when we have new data for a request. We just append it to the string
+// containing the previous data.
+size_t HTTPRequestImpl::writeCallback(void *const contents, const size_t size, const size_t nmemb, void *userp) {
+ assert(userp);
+ auto impl = reinterpret_cast<HTTPRequestImpl *>(userp);
+ MBGL_VERIFY_THREAD(impl->tid);
+
+ if (!impl->response) {
+ impl->response = util::make_unique<Response>();
+ }
+
+ impl->response->data.append((char *)contents, size * nmemb);
+ return size * nmemb;
+}
+
+// Compares the beginning of the (non-zero-terminated!) data buffer with the (zero-terminated!)
+// header string. If the data buffer contains the header string at the beginning, it returns
+// the length of the header string == begin of the value, otherwise it returns npos.
+// The comparison of the header is ASCII-case-insensitive.
+size_t headerMatches(const char *const header, const char *const buffer, const size_t length) {
+ const size_t headerLength = strlen(header);
+ if (length < headerLength) {
+ return std::string::npos;
+ }
+ size_t i = 0;
+ while (i < length && i < headerLength && std::tolower(buffer[i]) == std::tolower(header[i])) {
+ i++;
+ }
+ return i == headerLength ? i : std::string::npos;
+}
+
+int64_t parseCacheControl(const char *value) {
+ if (value) {
+ unsigned long long 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 0;
+}
+
+size_t HTTPRequestImpl::headerCallback(char *const buffer, const size_t size, const size_t nmemb, void *userp) {
+ assert(userp);
+ auto baton = reinterpret_cast<HTTPRequestImpl *>(userp);
+ MBGL_VERIFY_THREAD(baton->tid);
+
+ if (!baton->response) {
+ baton->response = util::make_unique<Response>();
+ }
+
+ const size_t length = size * nmemb;
+ size_t begin = std::string::npos;
+ if ((begin = headerMatches("last-modified: ", buffer, length)) != std::string::npos) {
+ // Always overwrite the modification date; We might already have a value here from the
+ // Date header, but this one is more accurate.
+ const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n
+ baton->response->modified = curl_getdate(value.c_str(), nullptr);
+ } else if ((begin = headerMatches("etag: ", buffer, length)) != std::string::npos) {
+ baton->response->etag = { buffer + begin, length - begin - 2 }; // remove \r\n
+ } else if ((begin = headerMatches("cache-control: ", buffer, length)) != std::string::npos) {
+ const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n
+ baton->response->expires = parseCacheControl(value.c_str());
+ } 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 = curl_getdate(value.c_str(), nullptr);
+ }
+
+ return length;
+}
+
+
+void HTTPRequestImpl::retry(uint64_t timeout) {
+ handleError(curl_multi_remove_handle(context->multi, handle));
+
+ response.reset();
+
+ assert(!timer);
+ timer = new uv_timer_t;
+ timer->data = this;
+ uv_timer_init(context->loop, timer);
+ uv_timer_start(timer, restart, timeout, 0);
+}
+
+void HTTPRequestImpl::retryImmediately() {
+ // All batons get notified when the network status changed, but some of them
+ // might not actually wait for the network to become available again.
+ if (timer && strategy == PreemptImmediately) {
+ // Triggers the timer upon the next event loop iteration.
+ uv_timer_stop(timer);
+ uv_timer_start(timer, restart, 0, 0);
+ }
+}
+
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+void HTTPRequestImpl::restart(uv_timer_t *timer, int) {
+#else
+void HTTPRequestImpl::restart(uv_timer_t *timer) {
+#endif
+ // Restart the request.
+ auto baton = reinterpret_cast<HTTPRequestImpl *>(timer->data);
+
+ // Get rid of the timer.
+ baton->timer = nullptr;
+ uv::close(timer);
+
+ baton->start();
+}
+
+void HTTPRequestImpl::finish(ResponseStatus status) {
+ if (status == ResponseStatus::TemporaryError && attempts < maxAttempts) {
+ strategy = ExponentialBackoff;
+ return retry((1 << (attempts - 1)) * 1000);
+ } else if (status == ResponseStatus::ConnectionError && attempts < maxAttempts) {
+ // By default, we will retry every 30 seconds (network change notification will
+ // preempt the timeout).
+ strategy = PreemptImmediately;
+ return retry(30000);
+ }
+
+ // Actually return the response.
+ if (status == ResponseStatus::NotModified) {
+ request->notify(std::move(response), FileCache::Hint::Refresh);
+ } else {
+ request->notify(std::move(response), FileCache::Hint::Full);
+ }
+
+ delete request;
+ delete this;
+}
+
+void HTTPRequestImpl::handleResult(CURLcode code) {
+ MBGL_VERIFY_THREAD(tid);
+
+ if (!request) {
+ // In this case, it doesn't make sense to even process the response even further since
+ // the request was canceled anyway.
+ delete this;
+ return;
+ }
+
+ // Make sure a response object exists in case we haven't got any headers
+ // or content.
+ if (!response) {
+ response = util::make_unique<Response>();
+ }
+
+ // Add human-readable error code
+ if (code != CURLE_OK) {
+ response->message = curl_easy_strerror(code);
+
+ switch (code) {
+ case CURLE_COULDNT_RESOLVE_PROXY:
+ case CURLE_COULDNT_RESOLVE_HOST:
+ case CURLE_COULDNT_CONNECT:
+ response->status = Response::Error;
+ return finish(ResponseStatus::ConnectionError);
+
+ case CURLE_OPERATION_TIMEDOUT:
+ response->status = Response::Error;
+ return finish(ResponseStatus::TemporaryError);
+
+ default:
+ response->status = Response::Error;
+ return finish(ResponseStatus::PermanentError);
+ }
+ } else {
+ long responseCode = 0;
+ curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &responseCode);
+
+ if (responseCode == 304) {
+ if (existingResponse) {
+ // We're going to reuse the old response object, but need to copy over the new
+ // expires value (if possible).
+ std::swap(response, existingResponse);
+ if (existingResponse->expires) {
+ response->expires = existingResponse->expires;
+ }
+ return finish(ResponseStatus::NotModified);
+ } else {
+ // This is an unsolicited 304 response and should only happen on malfunctioning
+ // HTTP servers. It likely doesn't include any data, but we don't have much options.
+ response->status = Response::Successful;
+ return finish(ResponseStatus::Successful);
+ }
+ } else if (responseCode == 200) {
+ response->status = Response::Successful;
+ return finish(ResponseStatus::Successful);
+ } else if (responseCode >= 500 && responseCode < 600) {
+ // Server errors may be temporary, so back off exponentially.
+ response->status = Response::Error;
+ response->message = "HTTP status code " + std::to_string(responseCode);
+ return finish(ResponseStatus::TemporaryError);
+ } else {
+ // We don't know how to handle any other errors, so declare them as permanently failing.
+ response->status = Response::Error;
+ response->message = "HTTP status code " + std::to_string(responseCode);
+ return finish(ResponseStatus::PermanentError);
+ }
+ }
+
+ throw std::runtime_error("Response hasn't been handled");
+}
+
+// -------------------------------------------------------------------------------------------------
+
+HTTPRequest::HTTPRequest(DefaultFileSource *source_, const Resource &resource_)
+ : SharedRequestBase(source_, resource_) {
+}
+
+HTTPRequest::~HTTPRequest() {
+ MBGL_VERIFY_THREAD(tid);
+
+ if (ptr) {
+ reinterpret_cast<HTTPRequestImpl *>(ptr)->abandon();
+ }
+}
+
+void HTTPRequest::start(uv_loop_t *loop, std::unique_ptr<Response> response) {
+ MBGL_VERIFY_THREAD(tid);
+
+ assert(!ptr);
+ ptr = new HTTPRequestImpl(this, loop, std::move(response));
+}
+
+void HTTPRequest::retryImmediately() {
+ MBGL_VERIFY_THREAD(tid);
+
+ if (ptr) {
+ reinterpret_cast<HTTPRequestImpl *>(ptr)->retryImmediately();
+ }
+}
+
+void HTTPRequest::cancel() {
+ MBGL_VERIFY_THREAD(tid);
+
+ if (ptr) {
+ delete reinterpret_cast<HTTPRequestImpl *>(ptr);
+ ptr = nullptr;
+ }
+
+ delete this;
+}
+
+}
diff --git a/src/mbgl/util/sqlite3.cpp b/platform/default/sqlite3.cpp
index 787db83992..6a27314c56 100644
--- a/src/mbgl/util/sqlite3.cpp
+++ b/platform/default/sqlite3.cpp
@@ -1,7 +1,28 @@
-#include <mbgl/util/sqlite3.hpp>
+#include "sqlite3.hpp"
#include <sqlite3.h>
#include <cassert>
+#include <cstring>
+
+// Check sqlite3 library version.
+const static bool sqliteVersionCheck = []() {
+ if (sqlite3_libversion_number() != SQLITE_VERSION_NUMBER) {
+ char message[96];
+ snprintf(message, 96,
+ "sqlite3 libversion mismatch: headers report %d, but library reports %d",
+ SQLITE_VERSION_NUMBER, sqlite3_libversion_number());
+ throw std::runtime_error(message);
+ }
+ if (strcmp(sqlite3_sourceid(), SQLITE_SOURCE_ID) != 0) {
+ char message[256];
+ snprintf(message, 256,
+ "sqlite3 sourceid mismatch: headers report \"%s\", but library reports \"%s\"",
+ SQLITE_SOURCE_ID, sqlite3_sourceid());
+ throw std::runtime_error(message);
+ }
+
+ return true;
+}();
namespace mapbox {
namespace sqlite {
diff --git a/src/mbgl/util/sqlite3.hpp b/platform/default/sqlite3.hpp
index 3e324f7ce1..3e324f7ce1 100644
--- a/src/mbgl/util/sqlite3.hpp
+++ b/platform/default/sqlite3.hpp
diff --git a/platform/default/sqlite_cache.cpp b/platform/default/sqlite_cache.cpp
new file mode 100644
index 0000000000..ab1ee040ff
--- /dev/null
+++ b/platform/default/sqlite_cache.cpp
@@ -0,0 +1,271 @@
+#include <mbgl/storage/default/sqlite_cache.hpp>
+#include <mbgl/storage/default/request.hpp>
+#include <mbgl/storage/response.hpp>
+
+#include <mbgl/util/util.hpp>
+#include <mbgl/util/async_queue.hpp>
+#include <mbgl/util/variant.hpp>
+#include <mbgl/platform/log.hpp>
+
+#include "sqlite3.hpp"
+#include "compression.hpp"
+
+#include <uv.h>
+
+#include <cassert>
+
+namespace mbgl {
+
+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);
+ }
+}
+
+std::string convertMapboxDomainsToProtocol(const std::string &url) {
+ const size_t protocol_separator = url.find("://");
+ if (protocol_separator == std::string::npos) {
+ return url;
+ }
+
+ const std::string protocol = url.substr(0, protocol_separator);
+ if (!(protocol == "http" || protocol == "https")) {
+ return url;
+ }
+
+ const size_t domain_begin = protocol_separator + 3;
+ const size_t path_separator = url.find("/", domain_begin);
+ if (path_separator == std::string::npos) {
+ return url;
+ }
+
+ const std::string domain = url.substr(domain_begin, path_separator - domain_begin);
+ if (domain.find(".tiles.mapbox.com") != std::string::npos) {
+ return "mapbox://" + url.substr(path_separator + 1);
+ } else {
+ return url;
+ }
+}
+
+std::string unifyMapboxURLs(const std::string &url) {
+ return removeAccessTokenFromURL(convertMapboxDomainsToProtocol(url));
+}
+
+
+using namespace mapbox::sqlite;
+
+struct SQLiteCache::GetAction {
+ const Resource resource;
+ const std::function<void(std::unique_ptr<Response>)> callback;
+};
+
+struct SQLiteCache::PutAction {
+ const Resource resource;
+ const std::shared_ptr<const Response> response;
+};
+
+struct SQLiteCache::RefreshAction {
+ const Resource resource;
+ const int64_t expires;
+};
+
+struct SQLiteCache::StopAction {
+};
+
+struct SQLiteCache::ActionDispatcher {
+ SQLiteCache &cache;
+ template <typename T> void operator()(T &t) { cache.process(t); }
+};
+
+SQLiteCache::SQLiteCache(const std::string &path_)
+ : path(path_),
+ loop(uv_loop_new()),
+ queue(new Queue(loop, [this](Action &action) {
+ mapbox::util::apply_visitor(ActionDispatcher{ *this }, action);
+ })),
+ thread([this]() {
+#ifdef __APPLE__
+ pthread_setname_np("SQLite Cache");
+#endif
+ uv_run(loop, UV_RUN_DEFAULT);
+ })
+{
+}
+
+SQLiteCache::~SQLiteCache() {
+ if (thread.joinable()) {
+ if (queue) {
+ queue->send(StopAction{ });
+ }
+ thread.join();
+ uv_loop_delete(loop);
+ }
+}
+
+
+void SQLiteCache::get(const Resource &resource, std::function<void(std::unique_ptr<Response>)> callback) {
+ // Can be called from any thread, but most likely from the file source thread.
+ // Will try to load the URL from the SQLite database and call the callback when done.
+ // Note that the callback is probably going to invoked from another thread, so the caller
+ // must make sure that it can run in that thread.
+ assert(queue);
+ queue->send(GetAction{ resource, callback });
+}
+
+void SQLiteCache::put(const Resource &resource, std::shared_ptr<const Response> response, Hint hint) {
+ // Can be called from any thread, but most likely from the file source thread. We are either
+ // storing a new response or updating the currently stored response, potentially setting a new
+ // expiry date.
+ assert(queue);
+ assert(response);
+
+ if (hint == Hint::Full) {
+ queue->send(PutAction{ resource, response });
+ } else if (hint == Hint::Refresh) {
+ queue->send(RefreshAction{ resource, response->expires });
+ }
+}
+
+void SQLiteCache::createDatabase() {
+ db = util::make_unique<Database>(path.c_str(), ReadWrite | Create);
+
+ constexpr const char *const sql = ""
+ "CREATE TABLE IF NOT EXISTS `http_cache` ("
+ " `url` TEXT PRIMARY KEY NOT NULL,"
+ " `status` INTEGER NOT NULL," // The response status (Successful or Error).
+ " `kind` INTEGER NOT NULL," // The kind of file.
+ " `modified` INTEGER," // Timestamp when the file was last modified.
+ " `etag` TEXT,"
+ " `expires` INTEGER," // Timestamp when the server says the file expires.
+ " `data` BLOB,"
+ " `compressed` INTEGER NOT NULL DEFAULT 0" // Whether the data is compressed.
+ ");"
+ "CREATE INDEX IF NOT EXISTS `http_cache_kind_idx` ON `http_cache` (`kind`);";
+
+ try {
+ db->exec(sql);
+ } catch(mapbox::sqlite::Exception &) {
+ // Creating the database table + index failed. That means there may already be one, likely
+ // with different columsn. Drop it and try to create a new one.
+ try {
+ db->exec("DROP TABLE IF EXISTS `http_cache`");
+ db->exec(sql);
+ } catch (mapbox::sqlite::Exception &ex) {
+ Log::Error(Event::Database, "Failed to create database: %s", ex.what());
+ db.release();
+ }
+ }
+}
+
+void SQLiteCache::process(GetAction &action) {
+ // This is called in the SQLite event loop.
+ if (!db) {
+ createDatabase();
+ }
+
+ if (!getStmt) {
+ // Initialize the statement 0 1
+ getStmt = util::make_unique<Statement>(db->prepare("SELECT `status`, `modified`, "
+ // 2 3 4 5 1
+ "`etag`, `expires`, `data`, `compressed` FROM `http_cache` WHERE `url` = ?"));
+ } else {
+ getStmt->reset();
+ }
+
+ const std::string unifiedURL = unifyMapboxURLs(action.resource.url);
+ getStmt->bind(1, unifiedURL.c_str());
+ if (getStmt->run()) {
+ // There is data.
+ auto response = util::make_unique<Response>();
+ response->status = Response::Status(getStmt->get<int>(0));
+ response->modified = getStmt->get<int64_t>(1);
+ response->etag = getStmt->get<std::string>(2);
+ response->expires = getStmt->get<int64_t>(3);
+ response->data = getStmt->get<std::string>(4);
+ if (getStmt->get<int>(5)) { // == compressed
+ response->data = util::decompress(response->data);
+ }
+ action.callback(std::move(response));
+ } else {
+ // There is no data.
+ action.callback(nullptr);
+ }
+}
+
+void SQLiteCache::process(PutAction &action) {
+ if (!db) {
+ createDatabase();
+ }
+
+ if (!putStmt) {
+ putStmt = util::make_unique<Statement>(db->prepare("REPLACE INTO `http_cache` ("
+ // 1 2 3 4 5 6 7 8
+ "`url`, `status`, `kind`, `modified`, `etag`, `expires`, `data`, `compressed`"
+ ") VALUES(?, ?, ?, ?, ?, ?, ?, ?)"));
+ } else {
+ putStmt->reset();
+ }
+
+ const std::string unifiedURL = unifyMapboxURLs(action.resource.url);
+ putStmt->bind(1 /* url */, unifiedURL.c_str());
+ putStmt->bind(2 /* status */, int(action.response->status));
+ putStmt->bind(3 /* kind */, int(action.resource.kind));
+ putStmt->bind(4 /* modified */, action.response->modified);
+ putStmt->bind(5 /* etag */, action.response->etag.c_str());
+ putStmt->bind(6 /* expires */, action.response->expires);
+
+ std::string data;
+ if (action.resource.kind != Resource::Image) {
+ // Do not compress images, since they are typically compressed already.
+ data = util::compress(action.response->data);
+ }
+
+ if (!data.empty() && data.size() < action.response->data.size()) {
+ // Store the compressed data when it is smaller than the original
+ // uncompressed data.
+ putStmt->bind(7 /* data */, data, false); // do not retain the string internally.
+ putStmt->bind(8 /* compressed */, true);
+ } else {
+ putStmt->bind(7 /* data */, action.response->data, false); // do not retain the string internally.
+ putStmt->bind(8 /* compressed */, false);
+ }
+
+ putStmt->run();
+}
+
+void SQLiteCache::process(RefreshAction &action) {
+ if (!db) {
+ createDatabase();
+ }
+
+ if (!refreshStmt) {
+ refreshStmt = util::make_unique<Statement>( // 1 2
+ db->prepare("UPDATE `http_cache` SET `expires` = ? WHERE `url` = ?"));
+ } else {
+ refreshStmt->reset();
+ }
+
+ const std::string unifiedURL = unifyMapboxURLs(action.resource.url);
+ refreshStmt->bind(1, int64_t(action.expires));
+ refreshStmt->bind(2, unifiedURL.c_str());
+ refreshStmt->run();
+}
+
+void SQLiteCache::process(StopAction &) {
+ assert(queue);
+ queue->stop();
+ queue = nullptr;
+}
+
+}
diff --git a/platform/default/uv_zip.c b/platform/default/uv_zip.c
new file mode 100644
index 0000000000..b6d2c2ebe0
--- /dev/null
+++ b/platform/default/uv_zip.c
@@ -0,0 +1,202 @@
+#include "uv_zip.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+
+
+void uv__zip_open_error(uv_zip_t *zip, int error) {
+ zip->result = -error;
+ if (zip->message) {
+ free((char *)zip->message);
+ zip->message = NULL;
+ }
+ const zip_uint64_t size = zip_error_to_str(NULL, 0, error, errno) + 1;
+ zip->message = malloc(size);
+ zip_error_to_str((char *)zip->message, size, error, errno);
+}
+
+void uv__zip_store_error(uv_zip_t *zip, const char *message) {
+ if (zip->message) {
+ free((char *)zip->message);
+ zip->message = NULL;
+ }
+ const unsigned long length = strlen(message);
+ zip->message = malloc(length);
+ strncpy((char *)zip->message, message, length);
+}
+
+void uv__zip_error(uv_zip_t *zip) {
+ int error;
+ zip_error_get(zip->archive, &error, NULL);
+ zip->result = -error;
+ uv__zip_store_error(zip, zip_strerror(zip->archive));
+}
+
+void uv__zip_file_error(uv_zip_t *zip) {
+ int error;
+ zip_file_error_get(zip->file, &error, NULL);
+ zip->result = -error;
+ uv__zip_store_error(zip, zip_file_strerror(zip->file));
+}
+
+void uv__zip_work_open(uv_work_t *req) {
+ uv_zip_t *zip = (uv_zip_t *)req->data;
+ assert(!zip->archive);
+
+ int error;
+ zip->archive = zip_open(zip->path, zip->flags, &error);
+ if (!zip->archive) {
+ uv__zip_open_error(zip, error);
+ } else {
+ zip->result = 0;
+ }
+}
+
+void uv__zip_work_fdopen(uv_work_t *req) {
+ uv_zip_t *zip = (uv_zip_t *)req->data;
+ assert(!zip->archive);
+
+ // extract the fd
+ uv_file fd = *(uv_file *)zip->path;
+ free((uv_file *)zip->path);
+ zip->path = NULL;
+
+ int error;
+ zip->archive = zip_fdopen(fd, zip->flags, &error);
+ if (!zip->archive) {
+ uv__zip_open_error(zip, error);
+ } else {
+ zip->result = 0;
+ }
+}
+
+void uv__zip_work_stat(uv_work_t *req) {
+ uv_zip_t *zip = (uv_zip_t *)req->data;
+ assert(zip->archive);
+ if (!zip->stat) {
+ zip->stat = malloc(sizeof(struct zip_stat));
+ zip_stat_init(zip->stat);
+ }
+ if (0 != zip_stat(zip->archive, zip->path, zip->flags, zip->stat)) {
+ uv__zip_error(zip);
+ }
+}
+
+void uv__zip_work_fopen(uv_work_t *req) {
+ uv_zip_t *zip = (uv_zip_t *)req->data;
+ assert(zip->archive);
+ zip->file = zip_fopen(zip->archive, zip->path, zip->flags);
+ if (!zip->file) {
+ uv__zip_error(zip);
+ }
+}
+
+void uv__zip_work_fread(uv_work_t *req) {
+ uv_zip_t *zip = (uv_zip_t *)req->data;
+ assert(zip->file);
+ assert(zip->buf);
+ zip->result = zip_fread(zip->file, zip->buf->base, zip->buf->len);
+ if (zip->result < 0) {
+ uv__zip_file_error(zip);
+ }
+}
+
+void uv__zip_work_fclose(uv_work_t *req) {
+ uv_zip_t *zip = (uv_zip_t *)req->data;
+ assert(zip->file);
+ zip->result = zip_fclose(zip->file);
+ if (zip->result != 0) {
+ uv__zip_file_error(zip);
+ }
+}
+
+void uv__zip_work_discard(uv_work_t *req) {
+ uv_zip_t *zip = (uv_zip_t *)req->data;
+ assert(zip->archive);
+ zip_discard(zip->archive);
+ zip->archive = NULL;
+ zip->result = 0;
+}
+
+void uv__zip_after_work(uv_work_t *req, int status) {
+ uv_zip_t *zip = (uv_zip_t *)req->data;
+ if (zip->cb) {
+ zip->cb(zip);
+ }
+}
+
+void uv_zip_init(uv_zip_t *zip) {
+ zip->work.data = zip;
+ zip->message = NULL;
+ zip->stat = NULL;
+ uv_zip_cleanup(zip);
+}
+
+void uv_zip_cleanup(uv_zip_t *zip) {
+ zip->data = NULL;
+ zip->flags = 0;
+ zip->result = 0;
+ zip->path = NULL;
+ zip->cb = NULL;
+ zip->archive = NULL;
+ zip->file = NULL;
+ zip->buf = NULL;
+
+ if (zip->message) {
+ free((char *)zip->message);
+ zip->message = NULL;
+ }
+
+ if (zip->stat) {
+ free(zip->stat);
+ zip->stat = NULL;
+ }
+}
+
+int uv_zip_open(uv_loop_t* loop, uv_zip_t *zip, const char *path, zip_flags_t flags, uv_zip_cb cb) {
+ zip->path = path;
+ zip->flags = flags;
+ zip->cb = cb;
+ return uv_queue_work(loop, &zip->work, uv__zip_work_open, uv__zip_after_work);
+}
+
+int uv_zip_fdopen(uv_loop_t* loop, uv_zip_t *zip, uv_file fd, int flags, uv_zip_cb cb) {
+ zip->path = malloc(sizeof(uv_file));
+ *(uv_file *)zip->path = fd;
+ zip->flags = flags;
+ zip->cb = cb;
+ return uv_queue_work(loop, &zip->work, uv__zip_work_fdopen, uv__zip_after_work);
+}
+
+int uv_zip_stat(uv_loop_t* loop, uv_zip_t *zip, const char *fname, zip_flags_t flags, uv_zip_cb cb) {
+ zip->path = fname;
+ zip->flags = flags;
+ zip->cb = cb;
+ return uv_queue_work(loop, &zip->work, uv__zip_work_stat, uv__zip_after_work);
+}
+
+int uv_zip_fopen(uv_loop_t* loop, uv_zip_t *zip, const char *fname, zip_flags_t flags, uv_zip_cb cb) {
+ zip->path = fname;
+ zip->flags = flags;
+ zip->cb = cb;
+ return uv_queue_work(loop, &zip->work, uv__zip_work_fopen, uv__zip_after_work);
+}
+
+int uv_zip_fclose(uv_loop_t* loop, uv_zip_t *zip, struct zip_file *file, uv_zip_cb cb) {
+ zip->file = file;
+ zip->cb = cb;
+ return uv_queue_work(loop, &zip->work, uv__zip_work_fclose, uv__zip_after_work);
+}
+
+int uv_zip_fread(uv_loop_t* loop, uv_zip_t *zip, struct zip_file *file, uv_buf_t *buf, uv_zip_cb cb) {
+ zip->file = file;
+ zip->buf = buf;
+ zip->cb = cb;
+ return uv_queue_work(loop, &zip->work, uv__zip_work_fread, uv__zip_after_work);
+}
+
+int uv_zip_discard(uv_loop_t* loop, uv_zip_t *zip, uv_zip_cb cb) {
+ zip->cb = cb;
+ return uv_queue_work(loop, &zip->work, uv__zip_work_discard, uv__zip_after_work);
+}
diff --git a/platform/default/uv_zip.h b/platform/default/uv_zip.h
new file mode 100644
index 0000000000..5908763f09
--- /dev/null
+++ b/platform/default/uv_zip.h
@@ -0,0 +1,45 @@
+#ifndef UV_ZIP
+#define UV_ZIP
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdlib.h>
+#include <uv.h>
+#include <zip.h>
+
+typedef struct uv_zip_s uv_zip_t;
+
+typedef void (*uv_zip_cb)(uv_zip_t* req);
+
+struct uv_zip_s {
+ uv_work_t work;
+ ssize_t result;
+ const char *message;
+ struct zip *archive;
+ struct zip_file *file;
+ struct zip_stat *stat;
+ void *data;
+ zip_flags_t flags;
+ const char *path;
+ uv_zip_cb cb;
+ uv_buf_t *buf;
+};
+
+void uv_zip_init(uv_zip_t *zip);
+void uv_zip_cleanup(uv_zip_t *zip);
+
+int uv_zip_open(uv_loop_t* loop, uv_zip_t *zip, const char *path, zip_flags_t flags, uv_zip_cb cb);
+int uv_zip_fdopen(uv_loop_t* loop, uv_zip_t *zip, uv_file fd, int flags, uv_zip_cb cb);
+int uv_zip_stat(uv_loop_t* loop, uv_zip_t *zip, const char *fname, zip_flags_t flags, uv_zip_cb cb);
+int uv_zip_fopen(uv_loop_t* loop, uv_zip_t *zip, const char *fname, zip_flags_t flags, uv_zip_cb cb);
+int uv_zip_fread(uv_loop_t* loop, uv_zip_t *zip, struct zip_file *file, uv_buf_t *buf, uv_zip_cb cb);
+int uv_zip_fclose(uv_loop_t* loop, uv_zip_t *zip, struct zip_file *file, uv_zip_cb cb);
+int uv_zip_discard(uv_loop_t* loop, uv_zip_t *zip, uv_zip_cb cb);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // UV_ZIP
diff --git a/platform/ios/cache_database_library.mm b/platform/ios/cache_database_library.mm
deleted file mode 100644
index 7989e73a4e..0000000000
--- a/platform/ios/cache_database_library.mm
+++ /dev/null
@@ -1,21 +0,0 @@
-#import <Foundation/Foundation.h>
-
-#include <mbgl/platform/platform.hpp>
-
-namespace mbgl {
-namespace platform {
-
-// Returns the path to the default cache database on this system.
-std::string defaultCacheDatabase() {
- NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
- if ([paths count] == 0) {
- // Disable the cache if we don't have a location to write.
- return "";
- }
-
- NSString *libraryDirectory = [paths objectAtIndex:0];
- return [[libraryDirectory stringByAppendingPathComponent:@"cache.db"] UTF8String];
-}
-
-}
-}
diff --git a/platform/osx/cache_database_application_support.mm b/platform/osx/cache_database_application_support.mm
deleted file mode 100644
index 974ea537cb..0000000000
--- a/platform/osx/cache_database_application_support.mm
+++ /dev/null
@@ -1,31 +0,0 @@
-#import <Foundation/Foundation.h>
-
-#include <mbgl/platform/platform.hpp>
-
-namespace mbgl {
-namespace platform {
-
-// Returns the path to the default cache database on this system.
-std::string defaultCacheDatabase() {
- NSArray *paths =
- NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
- if ([paths count] == 0) {
- // Disable the cache if we don't have a location to write.
- return "";
- }
-
- NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"Mapbox GL"];
-
- if (![[NSFileManager defaultManager] createDirectoryAtPath:path
- withIntermediateDirectories:YES
- attributes:nil
- error:nil]) {
- // Disable the cache if we couldn't create the directory.
- return "";
- }
-
- return [[path stringByAppendingPathComponent:@"cache.db"] UTF8String];
-}
-
-}
-}
diff --git a/scripts/android_env.sh b/scripts/android_env.sh
new file mode 100755
index 0000000000..93b1452a5a
--- /dev/null
+++ b/scripts/android_env.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+
+set -e
+set -o pipefail
+
+./scripts/local_mason.sh
+
+export MASON_PLATFORM=android
+
+export PATH=`.mason/mason env PATH`
+
+echo MASON_PLATFORM=\"android\"
+echo MASON_ANDROID_ABI=\"${MASON_ANDROID_ABI}\"
+
+echo CXX=\"`which $(.mason/mason env CXX)`\"
+echo CC=\"`which $(.mason/mason env CC)`\"
+echo LD=\"`which $(.mason/mason env LD)`\"
+echo LINK=\"`which $(.mason/mason env CXX)`\"
+echo AR=\"`which $(.mason/mason env AR)`\"
+echo RANLIB=\"`which $(.mason/mason env RANLIB)`\"
+echo STRIP=\"`which $(.mason/mason env STRIP)`\"
+echo LDFLAGS=\"`.mason/mason env LDFLAGS` ${LDFLAGS}\"
+echo CFLAGS=\"`.mason/mason env CFLAGS` ${CFLAGS}\"
+echo CPPFLAGS=\"`.mason/mason env CPPFLAGS` ${CPPFLAGS}\"
+echo JNIDIR=\"`.mason/mason env JNIDIR`\"
diff --git a/scripts/install_node.sh b/scripts/install_node.sh
new file mode 100755
index 0000000000..ecb5b88350
--- /dev/null
+++ b/scripts/install_node.sh
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+
+mason install node 0.10.35
+export PATH="`mason prefix node 0.10.35`/bin":"$PATH"
diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh
index e107000761..e4c9e77eac 100755
--- a/scripts/run_tests.sh
+++ b/scripts/run_tests.sh
@@ -3,34 +3,42 @@
set -e
set -o pipefail
-cd build/${BUILDTYPE:-Release}
+if [ `uname -s` = 'Darwin' ]; then HOST=${HOST:-osx} ; else HOST=${HOST:-linux} ; fi
-for TEST in ./test_* ; do
- # allow writing core files
- ulimit -c unlimited -S
- echo 'ulimit -c: '`ulimit -c`
+CMD=$1
+shift
+
+# allow writing core files
+ulimit -c unlimited -S
+echo 'ulimit -c: '`ulimit -c`
+if [ -f /proc/sys/kernel/core_pattern ]; then
echo '/proc/sys/kernel/core_pattern: '`cat /proc/sys/kernel/core_pattern`
+fi
- if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then
- sysctl kernel.core_pattern
- fi
+if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then
+ sysctl kernel.core_pattern
+fi
- RESULT=0
- ${TEST} || RESULT=$?
+# install test server dependencies
+if [ ! -d "test/node_modules/express" ]; then
+ (cd test; npm install express@4.11.1)
+fi
- if [[ ${RESULT} != 0 ]]; then
- echo "The program crashed with exit code ${RESULT}. We're now trying to output the core dump."
- fi
+RESULT=0
+${CMD} "$@" || RESULT=$?
- # output core dump if we got one
- for DUMP in $(find ./ -maxdepth 1 -name 'core*' -print); do
- gdb ${TEST} ${DUMP} -ex "thread apply all bt" -ex "set pagination 0" -batch
- rm -rf ${DUMP}
- done
+if [[ ${RESULT} != 0 ]]; then
+ echo "The program crashed with exit code ${RESULT}. We're now trying to output the core dump."
+fi
- # now we should present travis with the original
- # error code so the run cleanly stops
- if [[ ${RESULT} != 0 ]]; then
- exit $RESULT
- fi
+# output core dump if we got one
+for DUMP in $(find ./ -maxdepth 1 -name 'core*' -print); do
+ gdb ${CMD} ${DUMP} -ex "thread apply all bt" -ex "set pagination 0" -batch
+ rm -rf ${DUMP}
done
+
+# now we should present travis with the original
+# error code so the run cleanly stops
+if [[ ${RESULT} != 0 ]]; then
+ exit $RESULT
+fi
diff --git a/scripts/travis_script.sh b/scripts/travis_script.sh
index 308dfb8f1c..71635b5c7f 100755
--- a/scripts/travis_script.sh
+++ b/scripts/travis_script.sh
@@ -23,7 +23,7 @@ elif [[ ${TRAVIS_OS_NAME} == "linux" ]]; then
git submodule update --init test/suite
mapbox_time "run_tests" \
- ./scripts/run_tests.sh
+ make test-* BUILDTYPE=${BUILDTYPE}
mapbox_time "compare_results" \
./scripts/compare_images.sh
@@ -38,11 +38,8 @@ elif [[ ${TRAVIS_OS_NAME} == "osx" ]]; then
#
# build OS X
#
- mapbox_time "create_osx_project" \
- make build/macosx/mapboxgl-app.xcodeproj
-
- mapbox_time "build_osx" \
- xcodebuild -project ./build/macosx/mapboxgl-app.xcodeproj -jobs $JOBS
+ mapbox_time "build_osx_project" \
+ make xosx -j$JOBS
#
# build iOS
@@ -50,9 +47,6 @@ elif [[ ${TRAVIS_OS_NAME} == "osx" ]]; then
mapbox_time "checkout_cocoa_bindings" \
git submodule update --init ios/mapbox-gl-cocoa
- mapbox_time "create_ios_project" \
- make build/ios/mapbox-gl-cocoa/app/mapboxgl-app.xcodeproj
-
- mapbox_time "build_ios" \
- xcodebuild -project ./build/ios/mapbox-gl-cocoa/app/mapboxgl-app.xcodeproj -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO -jobs $JOBS
+ mapbox_time "build_ios_project" \
+ make ios -j$JOBS
fi
diff --git a/src/mbgl/geometry/buffer.hpp b/src/mbgl/geometry/buffer.hpp
index 0b28f77d0a..3649574bbf 100644
--- a/src/mbgl/geometry/buffer.hpp
+++ b/src/mbgl/geometry/buffer.hpp
@@ -80,7 +80,7 @@ protected:
}
}
pos += itemSize;
- return static_cast<char *>(array) + (pos - itemSize);
+ return reinterpret_cast<char *>(array) + (pos - itemSize);
}
// Get a pointer to the item at a given index.
@@ -92,7 +92,7 @@ protected:
if (i * itemSize >= pos) {
throw new std::runtime_error("Can't get element after array bounds");
} else {
- return static_cast<char *>(array) + (i * itemSize);
+ return reinterpret_cast<char *>(array) + (i * itemSize);
}
}
diff --git a/src/mbgl/geometry/elements_buffer.hpp b/src/mbgl/geometry/elements_buffer.hpp
index 9255337cb5..5c1b421d35 100644
--- a/src/mbgl/geometry/elements_buffer.hpp
+++ b/src/mbgl/geometry/elements_buffer.hpp
@@ -21,11 +21,6 @@ struct ElementGroup : public util::noncopyable {
: vertex_length(vertex_length_),
elements_length(elements_length_) {
}
-
- ElementGroup(ElementGroup &&rhs) noexcept
- : array(std::move(rhs.array)),
- vertex_length(rhs.vertex_length),
- elements_length(rhs.elements_length) {};
};
class TriangleElementsBuffer : public Buffer<
diff --git a/src/mbgl/geometry/vao.cpp b/src/mbgl/geometry/vao.cpp
index 0868d92515..00976b4d54 100644
--- a/src/mbgl/geometry/vao.cpp
+++ b/src/mbgl/geometry/vao.cpp
@@ -4,6 +4,9 @@
namespace mbgl {
+VertexArrayObject::VertexArrayObject() {
+}
+
VertexArrayObject::~VertexArrayObject() {
if (!gl::DeleteVertexArrays) return;
diff --git a/src/mbgl/geometry/vao.hpp b/src/mbgl/geometry/vao.hpp
index 2ecba731f7..72bacfd793 100644
--- a/src/mbgl/geometry/vao.hpp
+++ b/src/mbgl/geometry/vao.hpp
@@ -11,15 +11,8 @@ namespace mbgl {
class VertexArrayObject : public util::noncopyable {
public:
- inline VertexArrayObject() {};
-
- inline VertexArrayObject(VertexArrayObject &&rhs) noexcept
- : vao(rhs.vao),
- bound_shader(rhs.bound_shader),
- bound_shader_name(rhs.bound_shader_name),
- bound_vertex_buffer(rhs.bound_vertex_buffer),
- bound_elements_buffer(rhs.bound_elements_buffer),
- bound_offset(rhs.bound_offset) {};
+ VertexArrayObject();
+ ~VertexArrayObject();
template <typename Shader, typename VertexBuffer>
inline void bind(Shader& shader, VertexBuffer &vertexBuffer, char *offset) {
@@ -50,7 +43,9 @@ public:
}
}
- ~VertexArrayObject();
+ inline GLuint getID() const {
+ return vao;
+ }
private:
void bindVertexArrayObject();
diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp
index 3359dd317f..5aff7a2b78 100644
--- a/src/mbgl/map/map.cpp
+++ b/src/mbgl/map/map.cpp
@@ -24,6 +24,7 @@
#include <mbgl/platform/log.hpp>
#include <mbgl/util/string.hpp>
#include <mbgl/util/uv.hpp>
+#include <mbgl/util/mapbox.hpp>
#include <algorithm>
#include <iostream>
@@ -53,44 +54,12 @@ const static bool uvVersionCheck = []() {
return true;
}();
-
-#include <zlib.h>
-// Check zlib library version.
-const static bool zlibVersionCheck = []() {
- const char *const version = zlibVersion();
- if (version[0] != ZLIB_VERSION[0]) {
- throw std::runtime_error(mbgl::util::sprintf<96>(
- "zlib version mismatch: headers report %s, but library reports %s", ZLIB_VERSION, version));
- }
-
- return true;
-}();
-
-
-#include <sqlite3.h>
-// Check sqlite3 library version.
-const static bool sqliteVersionCheck = []() {
- if (sqlite3_libversion_number() != SQLITE_VERSION_NUMBER) {
- throw std::runtime_error(mbgl::util::sprintf<96>(
- "sqlite3 libversion mismatch: headers report %d, but library reports %d",
- SQLITE_VERSION_NUMBER, sqlite3_libversion_number()));
- }
- if (strcmp(sqlite3_sourceid(), SQLITE_SOURCE_ID) != 0) {
- throw std::runtime_error(mbgl::util::sprintf<256>(
- "sqlite3 sourceid mismatch: headers report \"%s\", but library reports \"%s\"",
- SQLITE_SOURCE_ID, sqlite3_sourceid()));
- }
-
- return true;
-}();
-
-
using namespace mbgl;
Map::Map(View& view_, FileSource& fileSource_)
: loop(util::make_unique<uv::loop>()),
view(view_),
-#ifndef NDEBUG
+#ifdef DEBUG
mainThread(std::this_thread::get_id()),
mapThread(mainThread),
#endif
@@ -127,9 +96,7 @@ Map::~Map() {
}
uv::worker &Map::getWorker() {
- if (!workers) {
- workers = util::make_unique<uv::worker>(**loop, 4, "Tile Worker");
- }
+ assert(workers);
return *workers;
}
@@ -153,8 +120,6 @@ void Map::start(bool startPaused) {
workers.reset();
activeSources.clear();
- fileSource.clearLoop();
-
terminating = true;
// Closes all open handles on the loop. This means that the loop will automatically terminate.
@@ -187,7 +152,7 @@ void Map::start(bool startPaused) {
}
thread = std::thread([this]() {
-#ifndef NDEBUG
+#ifdef DEBUG
mapThread = std::this_thread::get_id();
#endif
@@ -197,7 +162,7 @@ void Map::start(bool startPaused) {
run();
-#ifndef NDEBUG
+#ifdef DEBUG
mapThread = std::thread::id();
#endif
@@ -265,7 +230,7 @@ void Map::resume() {
void Map::run() {
if (mode == Mode::None) {
-#ifndef NDEBUG
+#ifdef DEBUG
mapThread = mainThread;
#endif
mode = Mode::Static;
@@ -277,6 +242,9 @@ void Map::run() {
}
view.activate();
+
+ workers = util::make_unique<uv::worker>(**loop, 4, "Tile Worker");
+
setup();
prepare();
@@ -297,11 +265,10 @@ void Map::run() {
// *after* all events have been processed.
if (mode == Mode::Static) {
render();
-#ifndef NDEBUG
+#ifdef DEBUG
mapThread = std::thread::id();
#endif
mode = Mode::None;
- fileSource.clearLoop();
}
view.deactivate();
@@ -386,13 +353,14 @@ void Map::setStyleJSON(std::string newStyleJSON, const std::string &base) {
style = std::make_shared<Style>();
}
+ style->base = base;
style->loadJSON((const uint8_t *)styleJSON.c_str());
style->cascadeClasses(classes);
- fileSource.setBase(base);
- glyphStore->setURL(style->glyph_url);
-
style->setDefaultTransitionDuration(defaultTransitionDuration);
+ const std::string glyphURL = util::mapbox::normalizeGlyphsURL(style->glyph_url, getAccessToken());
+ glyphStore->setURL(glyphURL);
+
update();
}
@@ -559,6 +527,15 @@ void Map::stopRotating() {
update();
}
+#pragma mark - Access Token
+
+void Map::setAccessToken(const std::string &token) {
+ accessToken = token;
+}
+
+const std::string &Map::getAccessToken() const {
+ return accessToken;
+}
#pragma mark - Toggles
@@ -674,20 +651,16 @@ void Map::updateTiles() {
source->source->update(*this, getWorker(),
style, *glyphAtlas, *glyphStore,
*spriteAtlas, getSprite(),
- *texturePool, fileSource, [this](){ update(); });
+ *texturePool, fileSource, ***loop, [this](){ update(); });
}
}
void Map::prepare() {
- if (!fileSource.hasLoop()) {
- fileSource.setLoop(**loop);
- }
-
if (!style) {
style = std::make_shared<Style>();
- fileSource.request(ResourceType::JSON, styleURL)->onload([&](const Response &res) {
- if (res.code == 200) {
+ fileSource.request({ Resource::Kind::JSON, styleURL}, **loop, [&](const Response &res) {
+ if (res.status == Response::Successful) {
// Calculate the base
const size_t pos = styleURL.rfind('/');
std::string base = "";
@@ -697,7 +670,7 @@ void Map::prepare() {
setStyleJSON(res.data, base);
} else {
- Log::Error(Event::Setup, "loading style failed: %ld (%s)", res.code, res.message.c_str());
+ Log::Error(Event::Setup, "loading style failed: %s", res.message.c_str());
}
});
}
diff --git a/src/mbgl/map/raster_tile_data.cpp b/src/mbgl/map/raster_tile_data.cpp
index 6fac7862e7..84e9bb236a 100644
--- a/src/mbgl/map/raster_tile_data.cpp
+++ b/src/mbgl/map/raster_tile_data.cpp
@@ -5,8 +5,8 @@
using namespace mbgl;
-RasterTileData::RasterTileData(Tile::ID const& id_, TexturePool& texturePool, const SourceInfo& source_)
- : TileData(id_, source_),
+RasterTileData::RasterTileData(Tile::ID const& id_, TexturePool& texturePool, const SourceInfo& source_, FileSource& fileSource_)
+ : TileData(id_, source_, fileSource_),
bucket(texturePool, properties) {
}
diff --git a/src/mbgl/map/raster_tile_data.hpp b/src/mbgl/map/raster_tile_data.hpp
index 42070d9c61..7f338056f5 100644
--- a/src/mbgl/map/raster_tile_data.hpp
+++ b/src/mbgl/map/raster_tile_data.hpp
@@ -16,7 +16,7 @@ class RasterTileData : public TileData {
friend class TileParser;
public:
- RasterTileData(Tile::ID const& id, TexturePool&, const SourceInfo&);
+ RasterTileData(Tile::ID const& id, TexturePool&, const SourceInfo&, FileSource &);
~RasterTileData();
virtual void parse();
diff --git a/src/mbgl/map/source.cpp b/src/mbgl/map/source.cpp
index 798cd41d1d..f23bcaa14a 100644
--- a/src/mbgl/map/source.cpp
+++ b/src/mbgl/map/source.cpp
@@ -15,6 +15,7 @@
#include <mbgl/geometry/glyph_atlas.hpp>
#include <mbgl/style/style_layer.hpp>
#include <mbgl/platform/log.hpp>
+#include <mbgl/util/uv_detail.hpp>
#include <mbgl/map/vector_tile_data.hpp>
#include <mbgl/map/raster_tile_data.hpp>
@@ -39,8 +40,9 @@ void Source::load(Map& map, FileSource& fileSource) {
util::ptr<Source> source = shared_from_this();
- fileSource.request(ResourceType::JSON, info.url)->onload([source, &map](const Response &res) {
- if (res.code != 200) {
+ const std::string url = util::mapbox::normalizeSourceURL(info.url, map.getAccessToken());
+ fileSource.request({ Resource::Kind::JSON, url }, **map.loop, [source, &map](const Response &res) {
+ if (res.status != Response::Successful) {
Log::Warning(Event::General, "failed to load source TileJSON");
return;
}
@@ -155,7 +157,7 @@ TileData::State Source::addTile(Map& map, uv::worker& worker,
util::ptr<Style> style,
GlyphAtlas& glyphAtlas, GlyphStore& glyphStore,
SpriteAtlas& spriteAtlas, util::ptr<Sprite> sprite,
- FileSource& fileSource, TexturePool& texturePool,
+ FileSource& fileSource, uv_loop_t &loop, TexturePool& texturePool,
const Tile::ID& id,
std::function<void ()> callback) {
const TileData::State state = hasTile(id);
@@ -188,14 +190,14 @@ TileData::State Source::addTile(Map& map, uv::worker& worker,
new_tile.data = std::make_shared<VectorTileData>(normalized_id, map.getMaxZoom(), style,
glyphAtlas, glyphStore,
spriteAtlas, sprite,
- texturePool, info);
+ texturePool, info, fileSource);
} else if (info.type == SourceType::Raster) {
- new_tile.data = std::make_shared<RasterTileData>(normalized_id, texturePool, info);
+ new_tile.data = std::make_shared<RasterTileData>(normalized_id, texturePool, info, fileSource);
} else {
throw std::runtime_error("source type not implemented");
}
- new_tile.data->request(worker, fileSource, map.getState().getPixelRatio(), callback);
+ new_tile.data->request(worker, loop, map.getState().getPixelRatio(), callback);
tile_data.emplace(new_tile.data->id, new_tile.data);
}
@@ -286,7 +288,7 @@ void Source::update(Map& map, uv::worker& worker,
util::ptr<Style> style,
GlyphAtlas& glyphAtlas, GlyphStore& glyphStore,
SpriteAtlas& spriteAtlas, util::ptr<Sprite> sprite,
- TexturePool& texturePool, FileSource& fileSource,
+ TexturePool& texturePool, FileSource& fileSource, uv_loop_t& loop,
std::function<void ()> callback) {
if (!loaded || map.getTime() <= updated)
return;
@@ -310,7 +312,7 @@ void Source::update(Map& map, uv::worker& worker,
const TileData::State state = addTile(map, worker, style,
glyphAtlas, glyphStore,
spriteAtlas, sprite,
- fileSource, texturePool,
+ fileSource, loop, texturePool,
id, callback);
if (state != TileData::State::parsed) {
diff --git a/src/mbgl/map/source.hpp b/src/mbgl/map/source.hpp
index f0023afa09..3649837a58 100644
--- a/src/mbgl/map/source.hpp
+++ b/src/mbgl/map/source.hpp
@@ -39,7 +39,7 @@ public:
util::ptr<Style>,
GlyphAtlas&, GlyphStore&,
SpriteAtlas&, util::ptr<Sprite>,
- TexturePool&, FileSource&,
+ TexturePool&, FileSource&, uv_loop_t& loop,
std::function<void ()> callback);
void updateMatrices(const mat4 &projMatrix, const TransformState &transform);
@@ -63,7 +63,7 @@ private:
util::ptr<Style>,
GlyphAtlas&, GlyphStore&,
SpriteAtlas&, util::ptr<Sprite>,
- FileSource&, TexturePool&,
+ FileSource&, uv_loop_t &, TexturePool&,
const Tile::ID&,
std::function<void ()> callback);
diff --git a/src/mbgl/map/sprite.cpp b/src/mbgl/map/sprite.cpp
index c1f71e59d9..19f7b7c8c6 100644
--- a/src/mbgl/map/sprite.cpp
+++ b/src/mbgl/map/sprite.cpp
@@ -52,6 +52,7 @@ Sprite::operator bool() const {
// The reason this isn't part of the constructor is that calling shared_from_this() in
// the constructor fails.
void Sprite::load(FileSource& fileSource) {
+
if (!valid) {
// Treat a non-existent sprite as a successfully loaded empty sprite.
loadedImage = true;
@@ -62,26 +63,26 @@ void Sprite::load(FileSource& fileSource) {
util::ptr<Sprite> sprite = shared_from_this();
- fileSource.request(ResourceType::JSON, jsonURL)->onload([sprite](const Response &res) {
- if (res.code == 200) {
+ fileSource.request({ Resource::Kind::JSON, jsonURL }, [sprite](const Response &res) {
+ if (res.status == Response::Successful) {
sprite->body = res.data;
sprite->parseJSON();
sprite->complete();
} else {
- Log::Warning(Event::Sprite, "Failed to load sprite info: Error %d: %s", res.code, res.message.c_str());
+ Log::Warning(Event::Sprite, "Failed to load sprite info: %s", res.message.c_str());
if (!sprite->future.valid()) {
sprite->promise.set_exception(std::make_exception_ptr(std::runtime_error(res.message)));
}
}
});
- fileSource.request(ResourceType::Image, spriteURL)->onload([sprite](const Response &res) {
- if (res.code == 200) {
+ fileSource.request({ Resource::Kind::Image, spriteURL }, [sprite](const Response &res) {
+ if (res.status == Response::Successful) {
sprite->image = res.data;
sprite->parseImage();
sprite->complete();
} else {
- Log::Warning(Event::Sprite, "Failed to load sprite image: Error %d: %s", res.code, res.message.c_str());
+ Log::Warning(Event::Sprite, "Failed to load sprite image: Error %s", res.message.c_str());
if (!sprite->future.valid()) {
sprite->promise.set_exception(std::make_exception_ptr(std::runtime_error(res.message)));
}
@@ -91,7 +92,6 @@ void Sprite::load(FileSource& fileSource) {
void Sprite::complete() {
if (loadedImage && loadedJSON) {
- Log::Info(Event::Sprite, "loaded %s", spriteURL.c_str());
promise.set_value();
}
}
diff --git a/src/mbgl/map/tile_data.cpp b/src/mbgl/map/tile_data.cpp
index f89ff15baf..9d48041239 100644
--- a/src/mbgl/map/tile_data.cpp
+++ b/src/mbgl/map/tile_data.cpp
@@ -6,29 +6,33 @@
#include <mbgl/util/string.hpp>
#include <mbgl/storage/file_source.hpp>
#include <mbgl/util/uv_detail.hpp>
+#include <mbgl/platform/log.hpp>
using namespace mbgl;
-TileData::TileData(Tile::ID const& id_, const SourceInfo& source_)
+TileData::TileData(Tile::ID const& id_, const SourceInfo& source_, FileSource& fileSource_)
: id(id_),
name(id),
state(State::initial),
source(source_),
+ fileSource(fileSource_),
debugBucket(debugFontBuffer) {
// Initialize tile debug coordinates
debugFontBuffer.addText(name.c_str(), 50, 200, 5);
}
TileData::~TileData() {
- cancel();
+ if (req) {
+ fileSource.cancel(req);
+ }
}
const std::string TileData::toString() const {
return std::string { "[tile " } + name + "]";
}
-void TileData::request(uv::worker& worker, FileSource& fileSource,
- float pixelRatio, std::function<void ()> callback) {
+void TileData::request(uv::worker &worker, uv_loop_t &loop,
+ float pixelRatio, std::function<void()> callback) {
if (source.tiles.empty())
return;
@@ -51,8 +55,7 @@ void TileData::request(uv::worker& worker, FileSource& fileSource,
// Note: Somehow this feels slower than the change to request_http()
std::weak_ptr<TileData> weak_tile = shared_from_this();
- req = fileSource.request(ResourceType::Tile, url);
- req->onload([weak_tile, url, callback, &worker](const Response &res) {
+ req = fileSource.request({ Resource::Kind::Tile, url }, &loop, [weak_tile, url, callback, &worker](const Response &res) {
util::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
@@ -61,9 +64,9 @@ void TileData::request(uv::worker& worker, FileSource& fileSource,
}
// Clear the request object.
- tile->req.reset();
+ tile->req = nullptr;
- if (res.code == 200) {
+ if (res.status == Response::Successful) {
tile->state = State::loaded;
tile->data = res.data;
@@ -71,9 +74,7 @@ void TileData::request(uv::worker& worker, FileSource& fileSource,
// Schedule tile parsing in another thread
tile->reparse(worker, callback);
} else {
-#if defined(DEBUG)
- fprintf(stderr, "[%s] tile loading failed: %ld, %s\n", url.c_str(), res.code, res.message.c_str());
-#endif
+ Log::Error(Event::HttpRequest, "[%s] tile loading failed: %s", url.c_str(), res.message.c_str());
}
});
}
@@ -81,10 +82,10 @@ void TileData::request(uv::worker& worker, FileSource& fileSource,
void TileData::cancel() {
if (state != State::obsolete) {
state = State::obsolete;
- if (req) {
- req->cancel();
- req.reset();
- }
+ }
+ if (req) {
+ fileSource.cancel(req);
+ req = nullptr;
}
}
diff --git a/src/mbgl/map/tile_data.hpp b/src/mbgl/map/tile_data.hpp
index 1ae215b204..a83a4648dd 100644
--- a/src/mbgl/map/tile_data.hpp
+++ b/src/mbgl/map/tile_data.hpp
@@ -18,6 +18,8 @@ namespace uv {
class worker;
}
+typedef struct uv_loop_s uv_loop_t;
+
namespace mbgl {
class Map;
@@ -46,10 +48,10 @@ public:
};
public:
- TileData(Tile::ID const& id, const SourceInfo&);
+ TileData(Tile::ID const& id, const SourceInfo&, FileSource&);
~TileData();
- void request(uv::worker&, FileSource&, float pixelRatio, std::function<void ()> callback);
+ void request(uv::worker&, uv_loop_t&, float pixelRatio, std::function<void ()> callback);
void reparse(uv::worker&, std::function<void ()> callback);
void cancel();
const std::string toString() const;
@@ -71,9 +73,10 @@ public:
public:
const SourceInfo& source;
+ FileSource& fileSource;
protected:
- std::unique_ptr<Request> req;
+ Request *req = nullptr;
std::string data;
// Contains the tile ID string for painting debug information.
diff --git a/src/mbgl/map/vector_tile_data.cpp b/src/mbgl/map/vector_tile_data.cpp
index 06782057f6..646ad7318a 100644
--- a/src/mbgl/map/vector_tile_data.cpp
+++ b/src/mbgl/map/vector_tile_data.cpp
@@ -5,6 +5,7 @@
#include <mbgl/style/style_layer.hpp>
#include <mbgl/style/style_bucket.hpp>
#include <mbgl/geometry/glyph_atlas.hpp>
+#include <mbgl/platform/log.hpp>
using namespace mbgl;
@@ -13,8 +14,8 @@ VectorTileData::VectorTileData(Tile::ID const& id_,
GlyphAtlas& glyphAtlas_, GlyphStore& glyphStore_,
SpriteAtlas& spriteAtlas_, util::ptr<Sprite> sprite_,
TexturePool& texturePool_,
- const SourceInfo& source_)
- : TileData(id_, source_),
+ const SourceInfo& source_, FileSource &fileSource_)
+ : TileData(id_, source_, fileSource_),
glyphAtlas(glyphAtlas_),
glyphStore(glyphStore_),
spriteAtlas(spriteAtlas_),
@@ -35,6 +36,10 @@ void VectorTileData::parse() {
}
try {
+ if (!style) {
+ throw std::runtime_error("style isn't present in VectorTileData object anymore");
+ }
+
// 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.
@@ -42,12 +47,13 @@ void VectorTileData::parse() {
glyphAtlas, glyphStore,
spriteAtlas, sprite,
texturePool);
+ // Clear the style so that we don't have a cycle in the shared_ptr references.
+ style.reset();
+
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();
+ Log::Error(Event::ParseTile, "Parsing [%d/%d/%d] failed: %s", id.z, id.x, id.y, ex.what());
+ state = State::obsolete;
return;
}
diff --git a/src/mbgl/map/vector_tile_data.hpp b/src/mbgl/map/vector_tile_data.hpp
index b9bf55a1b3..31318003af 100644
--- a/src/mbgl/map/vector_tile_data.hpp
+++ b/src/mbgl/map/vector_tile_data.hpp
@@ -36,7 +36,7 @@ public:
GlyphAtlas&, GlyphStore&,
SpriteAtlas&, util::ptr<Sprite>,
TexturePool&,
- const SourceInfo&);
+ const SourceInfo&, FileSource &);
~VectorTileData();
virtual void parse();
diff --git a/src/mbgl/renderer/fill_bucket.cpp b/src/mbgl/renderer/fill_bucket.cpp
index 3b55ca5a7d..b58e860b46 100644
--- a/src/mbgl/renderer/fill_bucket.cpp
+++ b/src/mbgl/renderer/fill_bucket.cpp
@@ -6,6 +6,7 @@
#include <mbgl/renderer/painter.hpp>
#include <mbgl/style/style.hpp>
#include <mbgl/map/vector_tile.hpp>
+#include <mbgl/util/std.hpp>
#include <mbgl/platform/gl.hpp>
@@ -111,12 +112,13 @@ void FillBucket::tessellate() {
throw geometry_too_long_exception();
}
- if (!lineGroups.size() || (lineGroups.back().vertex_length + total_vertex_count > 65535)) {
+ if (!lineGroups.size() || (lineGroups.back()->vertex_length + total_vertex_count > 65535)) {
// Move to a new group because the old one can't hold the geometry.
- lineGroups.emplace_back();
+ lineGroups.emplace_back(util::make_unique<line_group_type>());
}
- line_group_type& lineGroup = lineGroups.back();
+ assert(lineGroups.back());
+ line_group_type& lineGroup = *lineGroups.back();
uint32_t lineIndex = lineGroup.vertex_length;
for (const std::vector<ClipperLib::IntPoint>& polygon : polygons) {
@@ -157,14 +159,15 @@ void FillBucket::tessellate() {
}
}
- if (!triangleGroups.size() || (triangleGroups.back().vertex_length + total_vertex_count > 65535)) {
+ if (!triangleGroups.size() || (triangleGroups.back()->vertex_length + total_vertex_count > 65535)) {
// Move to a new group because the old one can't hold the geometry.
- triangleGroups.emplace_back();
+ triangleGroups.emplace_back(util::make_unique<triangle_group_type>());
}
// We're generating triangle fans, so we always start with the first
// coordinate in this polygon.
- triangle_group_type& triangleGroup = triangleGroups.back();
+ assert(triangleGroups.back());
+ triangle_group_type& triangleGroup = *triangleGroups.back();
uint32_t triangleIndex = triangleGroup.vertex_length;
for (int i = 0; i < triangle_count; ++i) {
@@ -215,32 +218,35 @@ bool FillBucket::hasData() const {
void FillBucket::drawElements(PlainShader& shader) {
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize);
char *elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize);
- for (triangle_group_type& group : triangleGroups) {
- group.array[0].bind(shader, vertexBuffer, triangleElementsBuffer, vertex_index);
- MBGL_CHECK_ERROR(glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index));
- vertex_index += group.vertex_length * vertexBuffer.itemSize;
- elements_index += group.elements_length * triangleElementsBuffer.itemSize;
+ for (auto& group : triangleGroups) {
+ assert(group);
+ group->array[0].bind(shader, vertexBuffer, triangleElementsBuffer, vertex_index);
+ MBGL_CHECK_ERROR(glDrawElements(GL_TRIANGLES, group->elements_length * 3, GL_UNSIGNED_SHORT, elements_index));
+ vertex_index += group->vertex_length * vertexBuffer.itemSize;
+ elements_index += group->elements_length * triangleElementsBuffer.itemSize;
}
}
void FillBucket::drawElements(PatternShader& shader) {
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize);
char *elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize);
- for (triangle_group_type& group : triangleGroups) {
- group.array[1].bind(shader, vertexBuffer, triangleElementsBuffer, vertex_index);
- MBGL_CHECK_ERROR(glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index));
- vertex_index += group.vertex_length * vertexBuffer.itemSize;
- elements_index += group.elements_length * triangleElementsBuffer.itemSize;
+ for (auto& group : triangleGroups) {
+ assert(group);
+ group->array[1].bind(shader, vertexBuffer, triangleElementsBuffer, vertex_index);
+ MBGL_CHECK_ERROR(glDrawElements(GL_TRIANGLES, group->elements_length * 3, GL_UNSIGNED_SHORT, elements_index));
+ vertex_index += group->vertex_length * vertexBuffer.itemSize;
+ elements_index += group->elements_length * triangleElementsBuffer.itemSize;
}
}
void FillBucket::drawVertices(OutlineShader& shader) {
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize);
char *elements_index = BUFFER_OFFSET(line_elements_start * lineElementsBuffer.itemSize);
- for (line_group_type& group : lineGroups) {
- group.array[0].bind(shader, vertexBuffer, lineElementsBuffer, vertex_index);
- MBGL_CHECK_ERROR(glDrawElements(GL_LINES, group.elements_length * 2, GL_UNSIGNED_SHORT, elements_index));
- vertex_index += group.vertex_length * vertexBuffer.itemSize;
- elements_index += group.elements_length * lineElementsBuffer.itemSize;
+ for (auto& group : lineGroups) {
+ assert(group);
+ group->array[0].bind(shader, vertexBuffer, lineElementsBuffer, vertex_index);
+ MBGL_CHECK_ERROR(glDrawElements(GL_LINES, group->elements_length * 2, GL_UNSIGNED_SHORT, elements_index));
+ vertex_index += group->vertex_length * vertexBuffer.itemSize;
+ elements_index += group->elements_length * lineElementsBuffer.itemSize;
}
}
diff --git a/src/mbgl/renderer/fill_bucket.hpp b/src/mbgl/renderer/fill_bucket.hpp
index ae766ec28d..e9762b7628 100644
--- a/src/mbgl/renderer/fill_bucket.hpp
+++ b/src/mbgl/renderer/fill_bucket.hpp
@@ -72,8 +72,8 @@ private:
const size_t line_elements_start;
VertexArrayObject array;
- std::vector<triangle_group_type> triangleGroups;
- std::vector<line_group_type> lineGroups;
+ std::vector<std::unique_ptr<triangle_group_type>> triangleGroups;
+ std::vector<std::unique_ptr<line_group_type>> lineGroups;
std::vector<ClipperLib::IntPoint> line;
bool hasVertices = false;
diff --git a/src/mbgl/renderer/line_bucket.cpp b/src/mbgl/renderer/line_bucket.cpp
index 4937c8ac63..8141e68e24 100644
--- a/src/mbgl/renderer/line_bucket.cpp
+++ b/src/mbgl/renderer/line_bucket.cpp
@@ -7,6 +7,7 @@
#include <mbgl/map/vector_tile.hpp>
#include <mbgl/util/math.hpp>
+#include <mbgl/util/std.hpp>
#include <mbgl/platform/gl.hpp>
#define BUFFER_OFFSET(i) ((char *)nullptr + (i))
@@ -306,12 +307,13 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) {
// Store the triangle/line groups.
{
- if (!triangleGroups.size() || (triangleGroups.back().vertex_length + vertex_count > 65535)) {
+ if (!triangleGroups.size() || (triangleGroups.back()->vertex_length + vertex_count > 65535)) {
// Move to a new group because the old one can't hold the geometry.
- triangleGroups.emplace_back();
+ triangleGroups.emplace_back(util::make_unique<triangle_group_type>());
}
- triangle_group_type& group = triangleGroups.back();
+ assert(triangleGroups.back());
+ triangle_group_type& group = *triangleGroups.back();
for (const TriangleElement& triangle : triangle_store) {
triangleElementsBuffer.add(
group.vertex_length + triangle.a,
@@ -326,12 +328,13 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) {
// Store the line join/cap groups.
{
- if (!pointGroups.size() || (pointGroups.back().vertex_length + vertex_count > 65535)) {
+ if (!pointGroups.size() || (pointGroups.back()->vertex_length + vertex_count > 65535)) {
// Move to a new group because the old one can't hold the geometry.
- pointGroups.emplace_back();
+ pointGroups.emplace_back(util::make_unique<point_group_type>());
}
- point_group_type& group = pointGroups.back();
+ assert(pointGroups.back());
+ point_group_type& group = *pointGroups.back();
for (PointElement point : point_store) {
pointElementsBuffer.add(group.vertex_length + point);
}
@@ -351,8 +354,9 @@ bool LineBucket::hasData() const {
bool LineBucket::hasPoints() const {
if (!pointGroups.empty()) {
- for (const point_group_type& group : pointGroups) {
- if (group.elements_length) {
+ for (const auto& group : pointGroups) {
+ assert(group);
+ if (group->elements_length) {
return true;
}
}
@@ -363,55 +367,59 @@ bool LineBucket::hasPoints() const {
void LineBucket::drawLines(LineShader& shader) {
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize);
char *elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize);
- for (triangle_group_type& group : triangleGroups) {
- if (!group.elements_length) {
+ for (auto& group : triangleGroups) {
+ assert(group);
+ if (!group->elements_length) {
continue;
}
- group.array[0].bind(shader, vertexBuffer, triangleElementsBuffer, vertex_index);
- MBGL_CHECK_ERROR(glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index));
- vertex_index += group.vertex_length * vertexBuffer.itemSize;
- elements_index += group.elements_length * triangleElementsBuffer.itemSize;
+ group->array[0].bind(shader, vertexBuffer, triangleElementsBuffer, vertex_index);
+ MBGL_CHECK_ERROR(glDrawElements(GL_TRIANGLES, group->elements_length * 3, GL_UNSIGNED_SHORT, elements_index));
+ vertex_index += group->vertex_length * vertexBuffer.itemSize;
+ elements_index += group->elements_length * triangleElementsBuffer.itemSize;
}
}
void LineBucket::drawLineSDF(LineSDFShader& shader) {
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize);
char *elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize);
- for (triangle_group_type& group : triangleGroups) {
- if (!group.elements_length) {
+ for (auto& group : triangleGroups) {
+ assert(group);
+ if (!group->elements_length) {
continue;
}
- group.array[2].bind(shader, vertexBuffer, triangleElementsBuffer, vertex_index);
- MBGL_CHECK_ERROR(glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index));
- vertex_index += group.vertex_length * vertexBuffer.itemSize;
- elements_index += group.elements_length * triangleElementsBuffer.itemSize;
+ group->array[2].bind(shader, vertexBuffer, triangleElementsBuffer, vertex_index);
+ MBGL_CHECK_ERROR(glDrawElements(GL_TRIANGLES, group->elements_length * 3, GL_UNSIGNED_SHORT, elements_index));
+ vertex_index += group->vertex_length * vertexBuffer.itemSize;
+ elements_index += group->elements_length * triangleElementsBuffer.itemSize;
}
}
void LineBucket::drawLinePatterns(LinepatternShader& shader) {
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize);
char *elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize);
- for (triangle_group_type& group : triangleGroups) {
- if (!group.elements_length) {
+ for (auto& group : triangleGroups) {
+ assert(group);
+ if (!group->elements_length) {
continue;
}
- group.array[1].bind(shader, vertexBuffer, triangleElementsBuffer, vertex_index);
- MBGL_CHECK_ERROR(glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index));
- vertex_index += group.vertex_length * vertexBuffer.itemSize;
- elements_index += group.elements_length * triangleElementsBuffer.itemSize;
+ group->array[1].bind(shader, vertexBuffer, triangleElementsBuffer, vertex_index);
+ MBGL_CHECK_ERROR(glDrawElements(GL_TRIANGLES, group->elements_length * 3, GL_UNSIGNED_SHORT, elements_index));
+ vertex_index += group->vertex_length * vertexBuffer.itemSize;
+ elements_index += group->elements_length * triangleElementsBuffer.itemSize;
}
}
void LineBucket::drawPoints(LinejoinShader& shader) {
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize);
char *elements_index = BUFFER_OFFSET(point_elements_start * pointElementsBuffer.itemSize);
- for (point_group_type& group : pointGroups) {
- if (!group.elements_length) {
+ for (auto& group : pointGroups) {
+ assert(group);
+ if (!group->elements_length) {
continue;
}
- group.array[0].bind(shader, vertexBuffer, pointElementsBuffer, vertex_index);
- MBGL_CHECK_ERROR(glDrawElements(GL_POINTS, group.elements_length, GL_UNSIGNED_SHORT, elements_index));
- vertex_index += group.vertex_length * vertexBuffer.itemSize;
- elements_index += group.elements_length * pointElementsBuffer.itemSize;
+ group->array[0].bind(shader, vertexBuffer, pointElementsBuffer, vertex_index);
+ MBGL_CHECK_ERROR(glDrawElements(GL_POINTS, group->elements_length, GL_UNSIGNED_SHORT, elements_index));
+ vertex_index += group->vertex_length * vertexBuffer.itemSize;
+ elements_index += group->elements_length * pointElementsBuffer.itemSize;
}
}
diff --git a/src/mbgl/renderer/line_bucket.hpp b/src/mbgl/renderer/line_bucket.hpp
index c4d0d4a050..3bc8bf399d 100644
--- a/src/mbgl/renderer/line_bucket.hpp
+++ b/src/mbgl/renderer/line_bucket.hpp
@@ -55,8 +55,8 @@ private:
const size_t triangle_elements_start;
const size_t point_elements_start;
- std::vector<triangle_group_type> triangleGroups;
- std::vector<point_group_type> pointGroups;
+ std::vector<std::unique_ptr<triangle_group_type>> triangleGroups;
+ std::vector<std::unique_ptr<point_group_type>> pointGroups;
};
}
diff --git a/src/mbgl/renderer/symbol_bucket.cpp b/src/mbgl/renderer/symbol_bucket.cpp
index 00ff4e4a3f..f53bbfa954 100644
--- a/src/mbgl/renderer/symbol_bucket.cpp
+++ b/src/mbgl/renderer/symbol_bucket.cpp
@@ -322,19 +322,19 @@ void SymbolBucket::addFeature(const std::vector<Coordinate> &line, const Shaping
collision.insert(glyphPlacement.boxes, anchor, glyphScale, glyphRange,
horizontalText);
}
- if (inside) addSymbols(text, glyphPlacement.shapes, glyphScale, glyphRange);
+ if (inside) addSymbols<TextBuffer, TextElementGroup>(text, glyphPlacement.shapes, glyphScale, glyphRange);
}
if (iconScale && std::isfinite(iconScale)) {
if (!properties.icon.ignore_placement) {
collision.insert(iconPlacement.boxes, anchor, iconScale, iconRange, horizontalIcon);
}
- if (inside) addSymbols(icon, iconPlacement.shapes, iconScale, iconRange);
+ if (inside) addSymbols<IconBuffer, IconElementGroup>(icon, iconPlacement.shapes, iconScale, iconRange);
}
}
}
-template <typename Buffer>
+template <typename Buffer, typename GroupType>
void SymbolBucket::addSymbols(Buffer &buffer, const PlacedGlyphs &symbols, float scale,
PlacementRange placementRange) {
const float zoom = collision.zoom;
@@ -366,14 +366,15 @@ void SymbolBucket::addSymbols(Buffer &buffer, const PlacedGlyphs &symbols, float
const int glyph_vertex_length = 4;
if (!buffer.groups.size() ||
- (buffer.groups.back().vertex_length + glyph_vertex_length > 65535)) {
+ (buffer.groups.back()->vertex_length + glyph_vertex_length > 65535)) {
// Move to a new group because the old one can't hold the geometry.
- buffer.groups.emplace_back();
+ buffer.groups.emplace_back(util::make_unique<GroupType>());
}
// We're generating triangle fans, so we always start with the first
// coordinate in this polygon.
- auto &triangleGroup = buffer.groups.back();
+ assert(buffer.groups.back());
+ auto &triangleGroup = *buffer.groups.back();
uint32_t triangleIndex = triangleGroup.vertex_length;
// coordinates (2 triangles)
@@ -398,33 +399,36 @@ void SymbolBucket::addSymbols(Buffer &buffer, const PlacedGlyphs &symbols, float
void SymbolBucket::drawGlyphs(SDFShader &shader) {
char *vertex_index = BUFFER_OFFSET(0);
char *elements_index = BUFFER_OFFSET(0);
- for (TextElementGroup &group : text.groups) {
- group.array[0].bind(shader, text.vertices, text.triangles, vertex_index);
- MBGL_CHECK_ERROR(glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index));
- vertex_index += group.vertex_length * text.vertices.itemSize;
- elements_index += group.elements_length * text.triangles.itemSize;
+ for (auto &group : text.groups) {
+ assert(group);
+ group->array[0].bind(shader, text.vertices, text.triangles, vertex_index);
+ MBGL_CHECK_ERROR(glDrawElements(GL_TRIANGLES, group->elements_length * 3, GL_UNSIGNED_SHORT, elements_index));
+ vertex_index += group->vertex_length * text.vertices.itemSize;
+ elements_index += group->elements_length * text.triangles.itemSize;
}
}
void SymbolBucket::drawIcons(SDFShader &shader) {
char *vertex_index = BUFFER_OFFSET(0);
char *elements_index = BUFFER_OFFSET(0);
- for (IconElementGroup &group : icon.groups) {
- group.array[0].bind(shader, icon.vertices, icon.triangles, vertex_index);
- MBGL_CHECK_ERROR(glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index));
- vertex_index += group.vertex_length * icon.vertices.itemSize;
- elements_index += group.elements_length * icon.triangles.itemSize;
+ for (auto &group : icon.groups) {
+ assert(group);
+ group->array[0].bind(shader, icon.vertices, icon.triangles, vertex_index);
+ MBGL_CHECK_ERROR(glDrawElements(GL_TRIANGLES, group->elements_length * 3, GL_UNSIGNED_SHORT, elements_index));
+ vertex_index += group->vertex_length * icon.vertices.itemSize;
+ elements_index += group->elements_length * icon.triangles.itemSize;
}
}
void SymbolBucket::drawIcons(IconShader &shader) {
char *vertex_index = BUFFER_OFFSET(0);
char *elements_index = BUFFER_OFFSET(0);
- for (IconElementGroup &group : icon.groups) {
- group.array[1].bind(shader, icon.vertices, icon.triangles, vertex_index);
- MBGL_CHECK_ERROR(glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index));
- vertex_index += group.vertex_length * icon.vertices.itemSize;
- elements_index += group.elements_length * icon.triangles.itemSize;
+ for (auto &group : icon.groups) {
+ assert(group);
+ group->array[1].bind(shader, icon.vertices, icon.triangles, vertex_index);
+ MBGL_CHECK_ERROR(glDrawElements(GL_TRIANGLES, group->elements_length * 3, GL_UNSIGNED_SHORT, elements_index));
+ vertex_index += group->vertex_length * icon.vertices.itemSize;
+ elements_index += group->elements_length * icon.triangles.itemSize;
}
}
}
diff --git a/src/mbgl/renderer/symbol_bucket.hpp b/src/mbgl/renderer/symbol_bucket.hpp
index bc148579fa..537c6d2097 100644
--- a/src/mbgl/renderer/symbol_bucket.hpp
+++ b/src/mbgl/renderer/symbol_bucket.hpp
@@ -81,7 +81,7 @@ private:
// Adds placed items to the buffer.
- template <typename Buffer>
+ template <typename Buffer, typename GroupType>
void addSymbols(Buffer &buffer, const PlacedGlyphs &symbols, float scale, PlacementRange placementRange);
// Adds glyphs to the glyph atlas so that they have a left/top/width/height coordinates associated to them that we can use for writing to a buffer.
@@ -95,16 +95,16 @@ public:
private:
Collision &collision;
- struct {
+ struct TextBuffer {
TextVertexBuffer vertices;
TriangleElementsBuffer triangles;
- std::vector<TextElementGroup> groups;
+ std::vector<std::unique_ptr<TextElementGroup>> groups;
} text;
- struct {
+ struct IconBuffer {
IconVertexBuffer vertices;
TriangleElementsBuffer triangles;
- std::vector<IconElementGroup> groups;
+ std::vector<std::unique_ptr<IconElementGroup>> groups;
} icon;
};
diff --git a/src/mbgl/storage/asset_request.hpp b/src/mbgl/storage/asset_request.hpp
deleted file mode 100644
index 3114d41ad2..0000000000
--- a/src/mbgl/storage/asset_request.hpp
+++ /dev/null
@@ -1,27 +0,0 @@
-#ifndef MBGL_STORAGE_ASSET_REQUEST
-#define MBGL_STORAGE_ASSET_REQUEST
-
-#include <mbgl/storage/base_request.hpp>
-
-namespace mbgl {
-
-typedef struct uv_loop_s uv_loop_t;
-
-struct AssetRequestBaton;
-
-class AssetRequest : public BaseRequest {
-public:
- AssetRequest(const std::string &path, uv_loop_t *loop);
- ~AssetRequest();
-
- void cancel();
-
-private:
- AssetRequestBaton *ptr = nullptr;
-
- friend struct AssetRequestBaton;
-};
-
-}
-
-#endif
diff --git a/src/mbgl/storage/base_request.cpp b/src/mbgl/storage/base_request.cpp
deleted file mode 100644
index 510bd7bf1c..0000000000
--- a/src/mbgl/storage/base_request.cpp
+++ /dev/null
@@ -1,87 +0,0 @@
-#include <mbgl/storage/base_request.hpp>
-#include <mbgl/storage/response.hpp>
-#include <mbgl/storage/request.hpp>
-#include <mbgl/util/std.hpp>
-
-#include <uv.h>
-
-#include <cassert>
-
-namespace mbgl {
-
-template <typename T, typename ...Args>
-void invoke(const std::forward_list<std::unique_ptr<Callback>> &list, Args&& ...args) {
- for (const std::unique_ptr<Callback> &callback : list) {
- assert(callback);
- if (callback->is<T>()) {
- callback->get<T>()(::std::forward<Args>(args)...);
- }
- }
-}
-
-BaseRequest::BaseRequest(const std::string &path_) : threadId(std::this_thread::get_id()), path(path_) {
-}
-
-// A base request can only be "canceled" by destroying the object. In that case, we'll have to
-// notify all cancel callbacks.
-BaseRequest::~BaseRequest() {
- assert(std::this_thread::get_id() == threadId);
- notify();
-}
-
-void BaseRequest::retryImmediately() {
- // no-op. override in child class.
-}
-
-void BaseRequest::notify() {
- assert(std::this_thread::get_id() == threadId);
-
- // 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.
- util::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.
- const std::forward_list<std::unique_ptr<Callback>> list = std::move(callbacks);
- callbacks.clear();
-
- if (response) {
- invoke<CompletedCallback>(list, *response);
- } else {
- invoke<AbortedCallback>(list);
- }
-
- self.reset();
-}
-
-Callback *BaseRequest::add(Callback &&callback, const util::ptr<BaseRequest> &request) {
- assert(std::this_thread::get_id() == threadId);
- assert(this == request.get());
-
- if (response) {
- // We already have a response. Notify right away.
- if (callback.is<CompletedCallback>()) {
- callback.get<CompletedCallback>()(*response);
- } else {
- // We already know that this request was successful. The AbortedCallback will be discarded
- // here since it would never be called.
- }
- return nullptr;
- } else {
- self = request;
- callbacks.push_front(util::make_unique<Callback>(std::move(callback)));
- return callbacks.front().get();
- }
-}
-
-void BaseRequest::remove(Callback *callback) {
- assert(std::this_thread::get_id() == threadId);
- callbacks.remove_if([=](const std::unique_ptr<Callback> &cb) {
- return cb.get() == callback;
- });
- if (callbacks.empty()) {
- self.reset();
- }
-}
-
-}
diff --git a/src/mbgl/storage/base_request.hpp b/src/mbgl/storage/base_request.hpp
deleted file mode 100644
index 5119c343e9..0000000000
--- a/src/mbgl/storage/base_request.hpp
+++ /dev/null
@@ -1,62 +0,0 @@
-#ifndef MBGL_STORAGE_BASE_REQUEST
-#define MBGL_STORAGE_BASE_REQUEST
-
-#include <mbgl/storage/request_callback.hpp>
-#include <mbgl/util/ptr.hpp>
-
-#include <string>
-#include <forward_list>
-#include <functional>
-#include <thread>
-
-typedef struct uv_loop_s uv_loop_t;
-typedef struct uv_async_s uv_async_t;
-
-namespace mbgl {
-
-class Response;
-class Request;
-
-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(const std::string &path);
- virtual ~BaseRequest();
-
- Callback *add(Callback &&callback, const util::ptr<BaseRequest> &request);
- void remove(Callback *callback);
-
- // Must be called by subclasses when a valid Response object is available. It will notify
- // all listeners.
- void notify();
-
- // This function is called when the request ought to be stopped. Any subclass must make sure this
- // is also called in its destructor. Calling this function repeatedly must be safe.
- // This function must call notify().
- virtual void cancel() = 0;
-
- // This function is called when the request should be reattempted immediately. This is typically
- // reaction to a network status change.
- virtual void retryImmediately();
-
-public:
- const std::thread::id threadId;
- const std::string path;
- std::unique_ptr<Response> response;
-
-protected:
- // This object may hold a shared_ptr to itself. It does this to prevent destruction of this object
- // while a request is in progress.
- util::ptr<BaseRequest> self;
- std::forward_list<std::unique_ptr<Callback>> callbacks;
-};
-
-}
-
-#endif
diff --git a/src/mbgl/storage/caching_http_file_source.cpp b/src/mbgl/storage/caching_http_file_source.cpp
deleted file mode 100644
index 634a56f9c4..0000000000
--- a/src/mbgl/storage/caching_http_file_source.cpp
+++ /dev/null
@@ -1,136 +0,0 @@
-#include <mbgl/storage/caching_http_file_source.hpp>
-#include <mbgl/storage/asset_request.hpp>
-#include <mbgl/storage/http_request.hpp>
-#include <mbgl/storage/sqlite_store.hpp>
-#include <mbgl/storage/asset_request.hpp>
-#include <mbgl/util/uv-messenger.h>
-#include <mbgl/util/mapbox.hpp>
-#include <mbgl/util/std.hpp>
-
-#include <uv.h>
-
-namespace mbgl {
-
-CachingHTTPFileSource::CachingHTTPFileSource(const std::string &path_)
- : path(path_) {}
-
-CachingHTTPFileSource::~CachingHTTPFileSource() {
-}
-
-void CachingHTTPFileSource::setLoop(uv_loop_t* loop_) {
- assert(!loop);
-
- threadId = std::this_thread::get_id();
- store = !path.empty() ? util::ptr<SQLiteStore>(new SQLiteStore(loop_, path)) : nullptr;
- 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)();
- });
- uv_unref((uv_handle_t *)&queue->async);
-}
-
-bool CachingHTTPFileSource::hasLoop() {
- return loop;
-}
-
-void CachingHTTPFileSource::clearLoop() {
- assert(std::this_thread::get_id() == threadId);
- assert(loop);
-
- uv_messenger_stop(queue, [](uv_messenger_t *msgr) {
- delete msgr;
- });
-
- util::ptr<BaseRequest> req;
-
- // Send a cancel() message to all requests that we are still holding.
- for (const std::pair<std::string, std::weak_ptr<BaseRequest>> &pair : pending) {
- if ((req = pair.second.lock())) {
- req->cancel();
- }
- }
-
- store.reset();
-
- loop = nullptr;
-}
-
-void CachingHTTPFileSource::setBase(std::string value) {
- // TODO: Make threadsafe.
- base.swap(value);
-}
-
-void CachingHTTPFileSource::setAccessToken(std::string value) {
- // TODO: Make threadsafe.
- accessToken.swap(value);
-}
-
-std::string CachingHTTPFileSource::getAccessToken() const {
- return accessToken;
-}
-
-std::unique_ptr<Request> CachingHTTPFileSource::request(ResourceType type, const std::string& url_) {
- assert(std::this_thread::get_id() == threadId);
-
- std::string url = url_;
-
- // Make URL absolute.
- const size_t separator = url.find("://");
- if (separator == std::string::npos) {
- url = base + url;
- }
-
- // Normalize mapbox:// URLs.
- switch (type) {
- case ResourceType::Glyphs:
- url = util::mapbox::normalizeGlyphsURL(url, accessToken);
- default:
- url = util::mapbox::normalizeSourceURL(url, accessToken);
- }
-
- util::ptr<BaseRequest> req;
-
- // First, try to find an existing Request object.
- auto it = pending.find(url);
- if (it != pending.end()) {
- req = it->second.lock();
- }
-
- if (!req) {
- if (url.substr(0, 8) == "asset://") {
- req = std::make_shared<AssetRequest>(url.substr(8), loop);
- } else {
- req = std::make_shared<HTTPRequest>(type, url, loop, store);
- }
-
- pending.emplace(url, req);
- }
-
- return util::make_unique<Request>(req);
-}
-
-void CachingHTTPFileSource::prepare(std::function<void()> fn) {
- if (std::this_thread::get_id() == threadId) {
- fn();
- } else {
- uv_messenger_send(queue, new std::function<void()>(std::move(fn)));
- }
-}
-
-void CachingHTTPFileSource::setReachability(bool reachable) {
- if (reachable && loop) {
- prepare([this]() {
- util::ptr<BaseRequest> req;
- for (const std::pair<std::string, std::weak_ptr<BaseRequest>> &pair : pending) {
- if ((req = pair.second.lock())) {
- req->retryImmediately();
- }
- }
- });
- }
-}
-
-}
diff --git a/src/mbgl/storage/default_file_source.cpp b/src/mbgl/storage/default_file_source.cpp
new file mode 100644
index 0000000000..e40aaff649
--- /dev/null
+++ b/src/mbgl/storage/default_file_source.cpp
@@ -0,0 +1,245 @@
+#include <mbgl/storage/default_file_source.hpp>
+#include <mbgl/storage/default/request.hpp>
+#include <mbgl/storage/default/asset_request.hpp>
+#include <mbgl/storage/default/http_request.hpp>
+
+#include <mbgl/storage/response.hpp>
+
+#include <mbgl/util/async_queue.hpp>
+#include <mbgl/util/util.hpp>
+
+#include <mbgl/util/variant.hpp>
+
+#pragma GCC diagnostic push
+#ifndef __clang__
+#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
+#pragma GCC diagnostic ignored "-Wshadow"
+#endif
+#include <boost/algorithm/string.hpp>
+#pragma GCC diagnostic pop
+
+#include <thread>
+#include <algorithm>
+#include <cassert>
+
+
+namespace algo = boost::algorithm;
+
+namespace mbgl {
+
+struct DefaultFileSource::ActionDispatcher {
+ DefaultFileSource &fileSource;
+ template <typename T> void operator()(T &t) { fileSource.process(t); }
+};
+
+struct DefaultFileSource::AddRequestAction {
+ Request *const request;
+};
+
+struct DefaultFileSource::RemoveRequestAction {
+ Request *const request;
+};
+
+struct DefaultFileSource::ResultAction {
+ const Resource resource;
+ std::unique_ptr<Response> response;
+};
+
+struct DefaultFileSource::StopAction {
+};
+
+
+DefaultFileSource::DefaultFileSource(FileCache *cache_)
+ : loop(uv_loop_new()),
+ cache(cache_),
+ queue(new Queue(loop, [this](Action &action) {
+ mapbox::util::apply_visitor(ActionDispatcher{*this}, action);
+ })),
+ thread([this]() {
+#ifdef __APPLE__
+ pthread_setname_np("FileSource");
+#endif
+ uv_run(loop, UV_RUN_DEFAULT);
+ }) {
+}
+
+DefaultFileSource::DefaultFileSource(FileCache *cache_, uv_loop_t *loop_)
+ : loop(loop_),
+ cache(cache_),
+ queue(new Queue(loop, [this](Action &action) {
+ mapbox::util::apply_visitor(ActionDispatcher{*this}, action);
+ })) {
+ // Make sure that the queue doesn't block the loop from exiting.
+ queue->unref();
+}
+
+DefaultFileSource::~DefaultFileSource() {
+ MBGL_VERIFY_THREAD(tid);
+
+ if (thread.joinable()) {
+ if (queue) {
+ queue->send(StopAction{ });
+ }
+ thread.join();
+ uv_loop_delete(loop);
+ } else {
+ // Assume that the loop we received is running in the current thread.
+ StopAction action {};
+ process(action);
+ }
+}
+
+SharedRequestBase *DefaultFileSource::find(const Resource &resource) {
+ // We're using a set of pointers here instead of a map between url and SharedRequestBase because
+ // we need to find the requests both by pointer and by URL. Given that the number of requests
+ // is generally very small (typically < 10 at a time), hashing by URL incurs too much overhead
+ // anyway.
+ const auto it = pending.find(resource);
+ if (it != pending.end()) {
+ return it->second;
+ }
+ return nullptr;
+}
+
+Request *DefaultFileSource::request(const Resource &resource, uv_loop_t *l, Callback callback) {
+ auto req = new Request(resource, l, std::move(callback));
+
+ // This function can be called from any thread. Make sure we're executing the actual call in the
+ // file source loop by sending it over the queue. It will be processed in processAction().
+ queue->send(AddRequestAction{ req });
+ return req;
+}
+
+void DefaultFileSource::request(const Resource &resource, Callback callback) {
+ auto req = new Request(resource, nullptr, std::move(callback));
+
+ // This function can be called from any thread. Make sure we're executing the actual call in the
+ // file source loop by sending it over the queue. It will be processed in processAction().
+ queue->send(AddRequestAction{ req });
+}
+
+void DefaultFileSource::cancel(Request *req) {
+ req->cancel();
+
+ // This function can be called from any thread. Make sure we're executing the actual call in the
+ // file source loop by sending it over the queue. It will be processed in processAction().
+ queue->send(RemoveRequestAction{ req });
+}
+
+void DefaultFileSource::process(AddRequestAction &action) {
+ const Resource &resource = action.request->resource;
+
+ // We're adding a new Request.
+ SharedRequestBase *sharedRequest = find(resource);
+ if (!sharedRequest) {
+ // There is no request for this URL yet. Create a new one and start it.
+ if (algo::starts_with(resource.url, "asset://")) {
+ sharedRequest = new AssetRequest(this, resource);
+ } else {
+ sharedRequest = new HTTPRequest(this, resource);
+ }
+
+ // Make sure the loop stays alive when we're not running the file source in it's own thread.
+ if (!thread.joinable() && pending.empty()) {
+ queue->ref();
+ }
+
+ const bool inserted = pending.emplace(resource, sharedRequest).second;
+ assert(inserted);
+ (void (inserted)); // silence unused variable warning on Release builds.
+
+ // But first, we're going to start querying the database if it exists.
+ if (!cache) {
+ sharedRequest->start(loop);
+ } else {
+ // Otherwise, first check the cache for existing data so that we can potentially
+ // revalidate the information without having to redownload everything.
+ cache->get(resource, [this, resource](std::unique_ptr<Response> response) {
+ queue->send(ResultAction { resource, std::move(response) });
+ });
+ }
+ }
+ sharedRequest->subscribe(action.request);
+}
+
+void DefaultFileSource::process(RemoveRequestAction &action) {
+ SharedRequestBase *sharedRequest = find(action.request->resource);
+ if (sharedRequest) {
+ // If the number of dependent requests of the SharedRequestBase drops to zero, the
+ // unsubscribe callback triggers the removal of the SharedRequestBase pointer from the list
+ // of pending requests and initiates cancelation.
+ sharedRequest->unsubscribe(action.request);
+ } else {
+ // There is no request for this URL anymore. Likely, the request already completed
+ // before we got around to process the cancelation request.
+ }
+
+ // Send a message back to the requesting thread and notify it that this request has been
+ // canceled and is now safe to be deleted.
+ action.request->destruct();
+}
+
+void DefaultFileSource::process(ResultAction &action) {
+ SharedRequestBase *sharedRequest = find(action.resource);
+ if (sharedRequest) {
+ if (action.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 (action.response->expires > now) {
+ // The response is fresh. We're good to notify the caller.
+ sharedRequest->notify(std::move(action.response), FileCache::Hint::No);
+ sharedRequest->cancel();
+ return;
+ } else {
+ // The cached response is stale. Now run the real request.
+ sharedRequest->start(loop, std::move(action.response));
+ }
+ } else {
+ // There is no response. Now run the real request.
+ sharedRequest->start(loop);
+ }
+ } else {
+ // There is no request for this URL anymore. Likely, the request was canceled
+ // before we got around to process the cache result.
+ }
+}
+
+void DefaultFileSource::process(StopAction &) {
+ // Cancel all remaining requests.
+ for (auto it : pending) {
+ it.second->unsubscribeAll();
+ }
+ pending.clear();
+
+ assert(queue);
+ queue->stop();
+ queue = nullptr;
+}
+
+void DefaultFileSource::notify(SharedRequestBase *sharedRequest,
+ const std::set<Request *> &observers,
+ std::shared_ptr<const Response> response, FileCache::Hint hint) {
+ // First, remove the request, since it might be destructed at any point now.
+ assert(find(sharedRequest->resource) == sharedRequest);
+ pending.erase(sharedRequest->resource);
+
+ if (response) {
+ if (cache) {
+ // Store response in database
+ cache->put(sharedRequest->resource, response, hint);
+ }
+
+ // Notify all observers.
+ for (auto it : observers) {
+ it->notify(response);
+ }
+ }
+
+ if (!thread.joinable() && pending.empty()) {
+ // When there are no pending requests, we're going to allow the queue to stop.
+ queue->unref();
+ }
+}
+
+}
diff --git a/src/mbgl/storage/http_request.cpp b/src/mbgl/storage/http_request.cpp
deleted file mode 100644
index 57e6c260ef..0000000000
--- a/src/mbgl/storage/http_request.cpp
+++ /dev/null
@@ -1,280 +0,0 @@
-#include <mbgl/storage/http_request.hpp>
-#include <mbgl/storage/sqlite_store.hpp>
-#include <mbgl/storage/http_request_baton.hpp>
-#include <mbgl/platform/log.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;
- util::ptr<SQLiteStore> store;
-};
-
-HTTPRequest::HTTPRequest(ResourceType type_, const std::string &path_, uv_loop_t *loop_, util::ptr<SQLiteStore> store_)
- : BaseRequest(path_), threadId(std::this_thread::get_id()), loop(loop_), store(store_), type(type_) {
- if (store) {
- startCacheRequest();
- } else {
- startHTTPRequest(nullptr);
- }
-}
-
-void HTTPRequest::startCacheRequest() {
- assert(std::this_thread::get_id() == threadId);
-
- cacheBaton = new CacheRequestBaton;
- cacheBaton->request = this;
- cacheBaton->path = path;
- cacheBaton->store = store;
- 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) {
- baton->request->cacheBaton = nullptr;
- baton->request->handleCacheResponse(std::move(response_));
- }
- }, cacheBaton);
-}
-
-void HTTPRequest::handleCacheResponse(std::unique_ptr<Response> &&res) {
- assert(std::this_thread::get_id() == threadId);
-
- if (res) {
- // 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 (res->expires > now) {
- response = std::move(res);
- notify();
- // Note: after calling notify(), the request object may cease to exist.
- // This HTTPRequest is completed.
- return;
- } else {
- // TODO: notify with preliminary results.
- }
- }
-
- startHTTPRequest(std::move(res));
-}
-
-void HTTPRequest::startHTTPRequest(std::unique_ptr<Response> &&res) {
- assert(std::this_thread::get_id() == threadId);
- assert(!httpBaton);
-
- httpBaton = std::make_shared<HTTPRequestBaton>(path);
- httpBaton->request = this;
- httpBaton->async = new uv_async_t;
- httpBaton->response = std::move(res);
- httpBaton->async->data = new util::ptr<HTTPRequestBaton>(httpBaton);
-
-#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
- uv_async_init(loop, httpBaton->async, [](uv_async_t *async, int) {
-#else
- uv_async_init(loop, httpBaton->async, [](uv_async_t *async) {
-#endif
- util::ptr<HTTPRequestBaton> &baton = *(util::ptr<HTTPRequestBaton> *)async->data;
-
- if (baton->request) {
- HTTPRequest *request = baton->request;
- request->httpBaton.reset();
- baton->request = nullptr;
- request->handleHTTPResponse(baton->type, std::move(baton->response));
- }
-
- delete (util::ptr<HTTPRequestBaton> *)async->data;
- uv_close((uv_handle_t *)async, [](uv_handle_t *handle) {
- uv_async_t *async_handle = (uv_async_t *)handle;
- delete async_handle;
- });
- });
- attempts++;
- HTTPRequestBaton::start(httpBaton);
-}
-
-
-
-void HTTPRequest::handleHTTPResponse(HTTPResponseType responseType, std::unique_ptr<Response> &&res) {
- assert(std::this_thread::get_id() == threadId);
- assert(!httpBaton);
- assert(!response);
-
- switch (responseType) {
- // This error was caused by a temporary error and it is likely that it will be resolved
- // immediately. We are going to try again right away. This is like the TemporaryError,
- // except that we will not perform exponential back-off.
- case HTTPResponseType::SingularError:
- if (attempts >= 4) {
- // Report as error after 4 attempts.
- response = std::move(res);
- notify();
- } else if (attempts >= 2) {
- // Switch to the back-off algorithm after the second failure.
- retryHTTPRequest(std::move(res), (1 << attempts) * 1000);
- return;
- } else {
- startHTTPRequest(std::move(res));
- }
- break;
-
- // This error might be resolved by waiting some time (e.g. server issues).
- // We are going to do an exponential back-off and will try again in a few seconds.
- case HTTPResponseType::TemporaryError:
- if (attempts >= 4) {
- // Report error back after it failed completely.
- response = std::move(res);
- notify();
- } else {
- retryHTTPRequest(std::move(res), (1 << attempts) * 1000);
- }
- break;
-
- // This error might be resolved once the network reachability status changes.
- // We are going to watch the network status for changes and will retry as soon as the
- // operating system notifies us of a network status change.
- case HTTPResponseType::ConnectionError:
-
- if (attempts >= 4) {
- // Report error back after it failed completely.
- response = std::move(res);
- notify();
- } else {
- // By default, we will retry every 60 seconds.
- retryHTTPRequest(std::move(res), 60000);
- }
- break;
-
- // The request was canceled programatically.
- case HTTPResponseType::Canceled:
- response.reset();
- notify();
- break;
-
- // This error probably won't be resolved by retrying anytime soon. We are giving up.
- case HTTPResponseType::PermanentError:
- response = std::move(res);
- notify();
- break;
-
- // The request returned data successfully. We retrieved and decoded the data successfully.
- case HTTPResponseType::Successful:
- if (store) {
- store->put(path, type, *res);
- }
- response = std::move(res);
- notify();
- break;
-
- // The request confirmed that the data wasn't changed. We already have the data.
- case HTTPResponseType::NotModified:
- if (store) {
- store->updateExpiration(path, res->expires);
- }
- response = std::move(res);
- notify();
- break;
-
- default:
- assert(!"Response wasn't set");
- break;
- }
-}
-
-using RetryBaton = std::pair<HTTPRequest *, std::unique_ptr<Response>>;
-
-void HTTPRequest::retryHTTPRequest(std::unique_ptr<Response> &&res, uint64_t timeout) {
- assert(std::this_thread::get_id() == threadId);
- assert(!backoffTimer);
- backoffTimer = new uv_timer_t();
- uv_timer_init(loop, backoffTimer);
- backoffTimer->data = new RetryBaton(this, std::move(res));
-
-#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
- uv_timer_start(backoffTimer, [](uv_timer_t *timer, int) {
-#else
- uv_timer_start(backoffTimer, [](uv_timer_t *timer) {
-#endif
- std::unique_ptr<RetryBaton> pair { static_cast<RetryBaton *>(timer->data) };
- pair->first->startHTTPRequest(std::move(pair->second));
- pair->first->backoffTimer = nullptr;
- uv_timer_stop(timer);
- uv_close((uv_handle_t *)timer, [](uv_handle_t *handle) { delete (uv_timer_t *)handle; });
- }, timeout, 0);
-}
-
-void HTTPRequest::removeHTTPBaton() {
- assert(std::this_thread::get_id() == threadId);
- if (httpBaton) {
- httpBaton->request = nullptr;
- HTTPRequestBaton::stop(httpBaton);
- httpBaton.reset();
- }
-}
-
-void HTTPRequest::removeCacheBaton() {
- assert(std::this_thread::get_id() == threadId);
- if (cacheBaton) {
- // Make sre 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.
- cacheBaton->request = nullptr;
- cacheBaton = nullptr;
- }
-}
-
-void HTTPRequest::removeBackoffTimer() {
- assert(std::this_thread::get_id() == threadId);
- if (backoffTimer) {
- delete static_cast<RetryBaton *>(backoffTimer->data);
- uv_timer_stop(backoffTimer);
- uv_close((uv_handle_t *)backoffTimer, [](uv_handle_t *handle) { delete (uv_timer_t *)handle; });
- backoffTimer = nullptr;
- }
-}
-
-void HTTPRequest::retryImmediately() {
- assert(std::this_thread::get_id() == threadId);
- if (!cacheBaton && !httpBaton) {
- if (backoffTimer) {
- // Retry immediately.
- uv_timer_stop(backoffTimer);
- std::unique_ptr<RetryBaton> pair { static_cast<RetryBaton *>(backoffTimer->data) };
- assert(pair->first == this);
- startHTTPRequest(std::move(pair->second));
- uv_close((uv_handle_t *)backoffTimer, [](uv_handle_t *handle) { delete (uv_timer_t *)handle; });
- backoffTimer = nullptr;
- } else {
- assert(!"We should always have a backoffTimer when there are no batons");
- }
- }
-}
-
-void HTTPRequest::cancel() {
- assert(std::this_thread::get_id() == threadId);
- removeCacheBaton();
- removeHTTPBaton();
- removeBackoffTimer();
- notify();
-}
-
-
-HTTPRequest::~HTTPRequest() {
- assert(std::this_thread::get_id() == threadId);
- cancel();
-}
-
-#pragma clang diagnostic pop
-
-}
diff --git a/src/mbgl/storage/http_request.hpp b/src/mbgl/storage/http_request.hpp
deleted file mode 100644
index 7cc72101d5..0000000000
--- a/src/mbgl/storage/http_request.hpp
+++ /dev/null
@@ -1,58 +0,0 @@
-#ifndef MBGL_STORAGE_HTTP_REQUEST
-#define MBGL_STORAGE_HTTP_REQUEST
-
-#include <mbgl/storage/resource_type.hpp>
-#include <mbgl/storage/base_request.hpp>
-#include <mbgl/storage/http_request_baton.hpp>
-
-#include <string>
-#include <memory>
-#include <cassert>
-#include <thread>
-
-typedef struct uv_loop_s uv_loop_t;
-typedef struct uv_timer_s uv_timer_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, util::ptr<SQLiteStore> store);
- ~HTTPRequest();
-
- void cancel();
- void retryImmediately();
-
-private:
- void startCacheRequest();
- void handleCacheResponse(std::unique_ptr<Response> &&response);
- void startHTTPRequest(std::unique_ptr<Response> &&res);
- void handleHTTPResponse(HTTPResponseType responseType, std::unique_ptr<Response> &&response);
-
- void retryHTTPRequest(std::unique_ptr<Response> &&res, uint64_t timeout);
-
- void removeCacheBaton();
- void removeHTTPBaton();
- void removeBackoffTimer();
-
-private:
- const std::thread::id threadId;
- uv_loop_t *const loop;
- CacheRequestBaton *cacheBaton = nullptr;
- util::ptr<HTTPRequestBaton> httpBaton;
- uv_timer_t *backoffTimer = nullptr;
- util::ptr<SQLiteStore> store;
- const ResourceType type;
- uint8_t attempts = 0;
-
- friend struct HTTPRequestBaton;
-};
-
-}
-
-#endif
diff --git a/src/mbgl/storage/http_request_baton.cpp b/src/mbgl/storage/http_request_baton.cpp
deleted file mode 100644
index d781a3bdf4..0000000000
--- a/src/mbgl/storage/http_request_baton.cpp
+++ /dev/null
@@ -1,12 +0,0 @@
-#include <mbgl/storage/http_request_baton.hpp>
-#include <uv.h>
-
-namespace mbgl {
-
-HTTPRequestBaton::HTTPRequestBaton(const std::string &path_) : threadId(std::this_thread::get_id()), path(path_) {
-}
-
-HTTPRequestBaton::~HTTPRequestBaton() {
-}
-
-}
diff --git a/src/mbgl/storage/network_status.cpp b/src/mbgl/storage/network_status.cpp
new file mode 100644
index 0000000000..04b6937d94
--- /dev/null
+++ b/src/mbgl/storage/network_status.cpp
@@ -0,0 +1,32 @@
+#include <mbgl/storage/network_status.hpp>
+
+#include <uv.h>
+
+// Example: Allocate a reachability object
+// Reachability* reach = [Reachability reachabilityForInternetConnection];
+// reach.reachableBlock = ^(Reachability* reach) { NetworkStatus::Reachable(); };
+// [reach startNotifier];
+
+namespace mbgl {
+
+std::mutex NetworkStatus::mtx;
+std::set<uv_async_t *> NetworkStatus::observers;
+
+void NetworkStatus::Subscribe(uv_async_t *async) {
+ std::lock_guard<std::mutex> lock(NetworkStatus::mtx);
+ observers.insert(async);
+}
+
+void NetworkStatus::Unsubscribe(uv_async_t *async) {
+ std::lock_guard<std::mutex> lock(NetworkStatus::mtx);
+ observers.erase(async);
+}
+
+void NetworkStatus::Reachable() {
+ std::lock_guard<std::mutex> lock(NetworkStatus::mtx);
+ for (auto async : observers) {
+ uv_async_send(async);
+ }
+}
+
+}
diff --git a/src/mbgl/storage/request.cpp b/src/mbgl/storage/request.cpp
index 39fbd36789..c1a57e7256 100644
--- a/src/mbgl/storage/request.cpp
+++ b/src/mbgl/storage/request.cpp
@@ -1,49 +1,99 @@
-#include <mbgl/storage/request.hpp>
-#include <mbgl/storage/base_request.hpp>
+#include <mbgl/storage/default/request.hpp>
+
+#include <mbgl/storage/response.hpp>
+
+#include <mbgl/util/util.hpp>
+#include <mbgl/util/uv.hpp>
#include <uv.h>
#include <cassert>
+#include <functional>
namespace mbgl {
-Request::Request(const util::ptr<BaseRequest> &base_)
- : thread_id(std::this_thread::get_id()), base(base_) {
+// Note: This requires that loop is running in the current thread (or not yet running).
+Request::Request(const Resource &resource_, uv_loop_t *loop, Callback callback_)
+ : callback(callback_), resource(resource_) {
+ // When there is no loop supplied (== nullptr), the callback will be fired in an arbitrary
+ // thread (the thread notify() is called from) rather than kicking back to the calling thread.
+ if (loop) {
+ notify_async = new uv_async_t;
+ notify_async->data = this;
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+ uv_async_init(loop, notify_async, [](uv_async_t *async, int) { notifyCallback(async); });
+#else
+ uv_async_init(loop, notify_async, notifyCallback);
+#endif
+ }
}
-Request::~Request() {
- assert(thread_id == std::this_thread::get_id());
+void Request::notifyCallback(uv_async_t *async) {
+ auto request = reinterpret_cast<Request *>(async->data);
+ uv::close(async);
+
+ if (!request->destruct_async) {
+ // We haven't created a cancel request, so we can safely delete this Request object
+ // since it won't be accessed in the future.
+ assert(request->response);
+ request->callback(*request->response);
+ delete request;
+ } else {
+ // Otherwise, we're waiting for for the destruct notification to be delivered in order
+ // to delete the Request object. We're doing this since we can't know whether the
+ // DefaultFileSource is still sending a cancel event, which means this object must still
+ // exist.
+ }
}
-void Request::onload(CompletedCallback cb) {
- assert(thread_id == std::this_thread::get_id());
- if (base) {
- Callback *callback = base->add(std::move(cb), base);
- if (callback) {
- callbacks.push_front(callback);
- }
+
+Request::~Request() {
+ if (notify_async) {
+ // Request objects can be destructed in other threads when the user didn't supply a loop.
+ MBGL_VERIFY_THREAD(tid)
}
}
-void Request::oncancel(AbortedCallback cb) {
- assert(thread_id == std::this_thread::get_id());
- if (base) {
- Callback *callback = base->add(std::move(cb), base);
- if (callback) {
- callbacks.push_front(callback);
- }
+void Request::notify(const std::shared_ptr<const Response> &response_) {
+ response = response_;
+ if (notify_async) {
+ uv_async_send(notify_async);
+ } else {
+ assert(response);
+ callback(*response);
+ delete this;
}
}
void Request::cancel() {
- assert(thread_id == std::this_thread::get_id());
- if (base) {
- for (Callback *callback : callbacks) {
- base->remove(callback);
- }
- base.reset();
+ MBGL_VERIFY_THREAD(tid)
+ assert(notify_async);
+ assert(!destruct_async);
+ destruct_async = new uv_async_t;
+ destruct_async->data = this;
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+ uv_async_init(notify_async->loop, destruct_async, [](uv_async_t *async, int) { cancelCallback(async); });
+#else
+ uv_async_init(notify_async->loop, destruct_async, cancelCallback);
+#endif
+}
+
+void Request::cancelCallback(uv_async_t *async) {
+ // The destruct_async will be invoked *after* the notify_async callback has already run.
+ auto request = reinterpret_cast<Request *>(async->data);
+ uv::close(async);
+ delete request;
+}
+
+// This gets called from the FileSource thread, and will only ever be invoked after cancel() was called
+// in the original requesting thread.
+void Request::destruct() {
+ if (notify_async) {
+ notify(nullptr);
}
- callbacks.clear();
+
+ assert(destruct_async);
+ uv_async_send(destruct_async);
}
}
diff --git a/src/mbgl/storage/response.cpp b/src/mbgl/storage/response.cpp
deleted file mode 100644
index a08a6d31ce..0000000000
--- a/src/mbgl/storage/response.cpp
+++ /dev/null
@@ -1,22 +0,0 @@
-#include <mbgl/storage/response.hpp>
-
-#include <chrono>
-
-namespace mbgl {
-
-int64_t Response::parseCacheControl(const char *value) {
- if (value) {
- unsigned long long 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/mbgl/storage/sqlite_store.cpp b/src/mbgl/storage/sqlite_store.cpp
deleted file mode 100644
index d382921dec..0000000000
--- a/src/mbgl/storage/sqlite_store.cpp
+++ /dev/null
@@ -1,228 +0,0 @@
-#include <mbgl/storage/sqlite_store.hpp>
-#include <mbgl/util/compression.hpp>
-#include <mbgl/util/sqlite3.hpp>
-#include <mbgl/util/std.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);
- }
-}
-
-std::string convertMapboxDomainsToProtocol(const std::string &url) {
- const size_t protocol_separator = url.find("://");
- if (protocol_separator == std::string::npos) {
- return url;
- }
-
- const std::string protocol = url.substr(0, protocol_separator);
- if (!(protocol == "http" || protocol == "https")) {
- return url;
- }
-
- const size_t domain_begin = protocol_separator + 3;
- const size_t path_separator = url.find("/", domain_begin);
- if (path_separator == std::string::npos) {
- return url;
- }
-
- const std::string domain = url.substr(domain_begin, path_separator - domain_begin);
- if (domain.find(".tiles.mapbox.com") != std::string::npos) {
- return "mapbox://" + url.substr(path_separator + 1);
- } else {
- return url;
- }
-}
-
-std::string unifyMapboxURLs(const std::string &url) {
- return removeAccessTokenFromURL(convertMapboxDomainsToProtocol(url));
-}
-
-namespace mbgl {
-
-SQLiteStore::SQLiteStore(uv_loop_t *loop, const std::string &path)
- : thread_id(std::this_thread::get_id()),
- db(std::make_shared<Database>(path.c_str(), ReadWrite | Create)) {
- createSchema();
- worker = new uv_worker_t;
- uv_worker_init(worker, loop, 1, "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, [](uv_worker_t *worker_handle) {
- delete worker_handle;
- });
- }
-}
-
-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,"
- " `etag` TEXT,"
- " `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 {
- util::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(std::this_thread::get_id() == 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 = unifyMapboxURLs(baton->path);
- // 0 1 2
- Statement stmt = baton->db->prepare("SELECT `code`, `type`, `modified`, "
- // 3 4 5 6
- "`etag`, `expires`, `data`, `compressed` FROM `http_cache` WHERE `url` = ?");
-
- stmt.bind(1, url.c_str());
- if (stmt.run()) {
- // There is data.
- baton->response = util::make_unique<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->etag = stmt.get<std::string>(3);
- baton->response->expires = stmt.get<int64_t>(4);
- baton->response->data = stmt.get<std::string>(5);
- if (stmt.get<int>(6)) { // == 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 {
- util::ptr<Database> db;
- std::string path;
- ResourceType type;
- Response response;
-};
-
-void SQLiteStore::put(const std::string &path, ResourceType type, const Response &response) {
- assert(std::this_thread::get_id() == 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 = unifyMapboxURLs(baton->path);
- Statement stmt = baton->db->prepare("REPLACE INTO `http_cache` ("
- // 1 2 3 4 5 6 7 8
- "`url`, `code`, `type`, `modified`, `etag`, `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.etag.c_str());
- stmt.bind(6, baton->response.expires);
-
- if (baton->type == ResourceType::Image) {
- stmt.bind(7, baton->response.data, false); // do not retain the string internally.
- stmt.bind(8, false);
- } else {
- stmt.bind(7, util::compress(baton->response.data), true); // retain the string internally.
- stmt.bind(8, true);
- }
-
- stmt.run();
- }, [](void *data) {
- delete (PutBaton *)data;
- });
-}
-
-struct ExpirationBaton {
- util::ptr<Database> db;
- std::string path;
- int64_t expires;
-};
-
-void SQLiteStore::updateExpiration(const std::string &path, int64_t expires) {
- assert(std::this_thread::get_id() == 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 = unifyMapboxURLs(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/mbgl/storage/sqlite_store.hpp b/src/mbgl/storage/sqlite_store.hpp
deleted file mode 100644
index 988eca2597..0000000000
--- a/src/mbgl/storage/sqlite_store.hpp
+++ /dev/null
@@ -1,49 +0,0 @@
-#ifndef MBGL_STORAGE_SQLITE_STORE
-#define MBGL_STORAGE_SQLITE_STORE
-
-#include <mbgl/storage/response.hpp>
-#include <mbgl/storage/resource_type.hpp>
-#include <mbgl/util/ptr.hpp>
-
-#include <uv.h>
-
-#include <string>
-#include <thread>
-
-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 std::thread::id thread_id;
- util::ptr<mapbox::sqlite::Database> db;
- uv_worker_t *worker = nullptr;
-};
-
-}
-
-#endif
diff --git a/src/mbgl/style/style.hpp b/src/mbgl/style/style.hpp
index af00f9710b..751a91be62 100644
--- a/src/mbgl/style/style.hpp
+++ b/src/mbgl/style/style.hpp
@@ -21,7 +21,7 @@ class Sprite;
class StyleLayer;
class StyleLayerGroup;
-class Style {
+class Style : public util::noncopyable {
public:
struct exception : std::runtime_error { exception(const char *msg) : std::runtime_error(msg) {} };
@@ -43,6 +43,7 @@ public:
util::ptr<StyleLayerGroup> layers;
std::vector<std::string> appliedClasses;
std::string glyph_url;
+ std::string base;
private:
std::string sprite_url;
diff --git a/src/mbgl/style/style_bucket.cpp b/src/mbgl/style/style_bucket.cpp
index 20d5d1147d..6e866f3035 100644
--- a/src/mbgl/style/style_bucket.cpp
+++ b/src/mbgl/style/style_bucket.cpp
@@ -2,12 +2,20 @@
namespace mbgl {
-StyleBucket::StyleBucket(StyleLayerType type_) :type(type_) {}
-
template<> const StyleBucketFill &defaultLayoutProperties() { static StyleBucketFill p; return p; }
template<> const StyleBucketLine &defaultLayoutProperties() { static StyleBucketLine p; return p; }
template<> const StyleBucketSymbol &defaultLayoutProperties() { static StyleBucketSymbol p; return p; }
template<> const StyleBucketRaster &defaultLayoutProperties() { static StyleBucketRaster p; return p; }
template<> const StyleBucketBackground &defaultLayoutProperties() { static StyleBucketBackground p; return p; }
+StyleBucket::StyleBucket(StyleLayerType type_) : type(type_) {
+ switch (type) {
+ case StyleLayerType::Fill: render.set<StyleBucketFill>(); break;
+ case StyleLayerType::Line: render.set<StyleBucketLine>(); break;
+ case StyleLayerType::Symbol: render.set<StyleBucketSymbol>(); break;
+ case StyleLayerType::Raster: render.set<StyleBucketRaster>(); break;
+ default: break;
+ }
+}
+
} \ No newline at end of file
diff --git a/src/mbgl/style/style_bucket.hpp b/src/mbgl/style/style_bucket.hpp
index 08884c2e09..eac899cb97 100644
--- a/src/mbgl/style/style_bucket.hpp
+++ b/src/mbgl/style/style_bucket.hpp
@@ -19,10 +19,23 @@ class Source;
class StyleBucketFill {
public:
+ // Make movable only.
+ StyleBucketFill() = default;
+ StyleBucketFill(StyleBucketFill &&) = default;
+ StyleBucketFill& operator=(StyleBucketFill &&) = default;
+ StyleBucketFill(const StyleBucketFill &) = delete;
+ StyleBucketFill& operator=(const StyleBucketFill &) = delete;
};
class StyleBucketLine {
public:
+ // Make movable only.
+ StyleBucketLine() = default;
+ StyleBucketLine(StyleBucketLine &&) = default;
+ StyleBucketLine& operator=(StyleBucketLine &&) = default;
+ StyleBucketLine(const StyleBucketLine &) = delete;
+ StyleBucketLine& operator=(const StyleBucketLine &) = delete;
+
CapType cap = CapType::Butt;
JoinType join = JoinType::Miter;
float miter_limit = 2.0f;
@@ -32,11 +45,11 @@ public:
class StyleBucketSymbol {
public:
// Make movable only.
- inline StyleBucketSymbol() = default;
- inline StyleBucketSymbol(StyleBucketSymbol &&) = default;
- inline StyleBucketSymbol& operator=(StyleBucketSymbol &&) = default;
- inline StyleBucketSymbol(const StyleBucketSymbol &) = delete;
- inline StyleBucketSymbol& operator=(const StyleBucketSymbol &) = delete;
+ StyleBucketSymbol() = default;
+ StyleBucketSymbol(StyleBucketSymbol &&) = default;
+ StyleBucketSymbol& operator=(StyleBucketSymbol &&) = default;
+ StyleBucketSymbol(const StyleBucketSymbol &) = delete;
+ StyleBucketSymbol& operator=(const StyleBucketSymbol &) = delete;
PlacementType placement = PlacementType::Point;
float min_distance = 250.0f;
@@ -79,6 +92,12 @@ public:
class StyleBucketRaster {
public:
+ // Make movable only.
+ StyleBucketRaster() = default;
+ StyleBucketRaster(StyleBucketRaster &&) = default;
+ StyleBucketRaster& operator=(StyleBucketRaster &&) = default;
+ StyleBucketRaster(const StyleBucketRaster &) = delete;
+ StyleBucketRaster& operator=(const StyleBucketRaster &) = delete;
};
class StyleBucketBackground {
@@ -89,7 +108,7 @@ typedef mapbox::util::variant<StyleBucketFill, StyleBucketLine, StyleBucketSymbo
StyleBucketRaster, StyleBucketBackground, std::false_type> StyleBucketRender;
-class StyleBucket {
+class StyleBucket : public util::noncopyable {
public:
typedef util::ptr<StyleBucket> Ptr;
diff --git a/src/mbgl/text/glyph_store.cpp b/src/mbgl/text/glyph_store.cpp
index 2f5db180fd..0d9e70d556 100644
--- a/src/mbgl/text/glyph_store.cpp
+++ b/src/mbgl/text/glyph_store.cpp
@@ -148,28 +148,23 @@ GlyphPBF::GlyphPBF(const std::string &glyphURL, const std::string &fontStack, Gl
});
// The prepare call jumps back to the main thread.
- fileSource.prepare([&, url] {
- auto request = fileSource.request(ResourceType::Glyphs, url);
- request->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 = std::string { "[ERROR] failed to load glyphs (" } + util::toString(res.code) + "): " + res.message;
- 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);
- }
- });
- request->oncancel([&]() {
- promise.set_exception(std::make_exception_ptr(std::runtime_error("Loading glyphs was canceled")));
- });
+ fileSource.request({ Resource::Kind::Glyphs, url }, [&, url](const Response &res) {
+ if (res.status != Response::Successful) {
+ // Something went wrong with loading the glyph pbf. Pass on the error to the future listeners.
+ const std::string msg = std::string { "[ERROR] failed to load glyphs: " } + res.message;
+ 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);
+ }
});
}
+
std::shared_future<GlyphPBF &> GlyphPBF::getFuture() {
return future;
}
diff --git a/src/mbgl/util/merge_lines.cpp b/src/mbgl/util/merge_lines.cpp
new file mode 100644
index 0000000000..7eb8306707
--- /dev/null
+++ b/src/mbgl/util/merge_lines.cpp
@@ -0,0 +1,99 @@
+#include "merge_lines.hpp"
+#include <sstream>
+
+namespace mbgl {
+namespace util {
+
+unsigned int mergeFromRight(std::vector<SymbolFeature> &features,
+ std::map<std::string, unsigned int> &rightIndex,
+ std::map<std::string, unsigned int>::iterator left,
+ std::string &rightKey,
+ std::vector<std::vector<Coordinate>> &geom) {
+
+ unsigned int index = left->second;
+ rightIndex.erase(left);
+ rightIndex[rightKey] = index;
+ features[index].geometry[0].pop_back();
+ features[index].geometry[0].insert(
+ features[index].geometry[0].end(), geom[0].begin(), geom[0].end());
+ geom[0].clear();
+ return index;
+}
+
+unsigned int mergeFromLeft(std::vector<SymbolFeature> &features,
+ std::map<std::string, unsigned int> &leftIndex,
+ std::string &leftKey,
+ std::map<std::string, unsigned int>::iterator right,
+ std::vector<std::vector<Coordinate>> &geom) {
+
+ unsigned int index = right->second;
+ leftIndex.erase(right);
+ leftIndex[leftKey] = index;
+ geom[0].pop_back();
+ geom[0].insert(
+ geom[0].end(), features[index].geometry[0].begin(), features[index].geometry[0].end());
+ features[index].geometry[0].clear();
+ std::swap(features[index].geometry[0], geom[0]);
+ return index;
+}
+
+std::string
+getKey(const std::u32string &text, const std::vector<std::vector<Coordinate>> &geom, bool onRight) {
+ const Coordinate &coord = onRight ? geom[0].back() : geom[0].front();
+ std::ostringstream key;
+ for (const char32_t &c : text) {
+ key << (char)c;
+ }
+ key << ":" << coord.x << ":" << coord.y;
+ return key.str();
+}
+
+void mergeLines(std::vector<SymbolFeature> &features) {
+
+ std::map<std::string, unsigned int> leftIndex;
+ std::map<std::string, unsigned int> rightIndex;
+
+ for (unsigned int k = 0; k < features.size(); k++) {
+ SymbolFeature &feature = features[k];
+ std::vector<std::vector<Coordinate>> &geometry = feature.geometry;
+
+ if (!feature.label.length()) {
+ continue;
+ }
+
+ std::string leftKey = getKey(feature.label, geometry, false);
+ std::string rightKey = getKey(feature.label, geometry, true);
+
+ auto left = rightIndex.find(leftKey);
+ auto right = leftIndex.find(rightKey);
+
+ if ((left != rightIndex.end()) && (right != leftIndex.end()) &&
+ (left->second != right->second)) {
+ // found lines with the same text adjacent to both ends of the current line, merge all
+ // three
+ unsigned int j = mergeFromLeft(features, leftIndex, leftKey, right, geometry);
+ unsigned int i =
+ mergeFromRight(features, rightIndex, left, rightKey, features[j].geometry);
+
+ leftIndex.erase(leftKey);
+ rightIndex.erase(rightKey);
+ rightIndex[getKey(feature.label, features[i].geometry, true)] = i;
+
+ } else if (left != rightIndex.end()) {
+ // found mergeable line adjacent to the start of the current line, merge
+ mergeFromRight(features, rightIndex, left, rightKey, geometry);
+
+ } else if (right != leftIndex.end()) {
+ // found mergeable line adjacent to the end of the current line, merge
+ mergeFromLeft(features, leftIndex, leftKey, right, geometry);
+
+ } else {
+ // no adjacent lines, add as a new item
+ leftIndex[leftKey] = k;
+ rightIndex[rightKey] = k;
+ }
+ }
+}
+
+} // end namespace util
+} // end namespace mbgl
diff --git a/src/mbgl/util/merge_lines.hpp b/src/mbgl/util/merge_lines.hpp
index e0dd4c6415..4460a0bcea 100644
--- a/src/mbgl/util/merge_lines.hpp
+++ b/src/mbgl/util/merge_lines.hpp
@@ -4,102 +4,24 @@
#include <map>
#include <string>
#include <vector>
-#include <sstream>
#include <mbgl/renderer/symbol_bucket.hpp>
-
namespace mbgl {
namespace util {
-unsigned int mergeFromRight(
- std::vector<SymbolFeature> &features,
- std::map<std::string,unsigned int> &rightIndex,
- std::map<std::string,unsigned int>::iterator left,
- std::string &rightKey,
- std::vector<std::vector<Coordinate>> &geom) {
-
- unsigned int index = left->second;
- rightIndex.erase(left);
- rightIndex[rightKey] = index;
- features[index].geometry[0].pop_back();
- features[index].geometry[0].insert(features[index].geometry[0].end(), geom[0].begin(), geom[0].end());
- geom[0].clear();
- return index;
-}
-
-unsigned int mergeFromLeft(
- std::vector<SymbolFeature> &features,
- std::map<std::string,unsigned int> &leftIndex,
- std::string &leftKey,
- std::map<std::string,unsigned int>::iterator right,
- std::vector<std::vector<Coordinate>> &geom) {
-
- unsigned int index = right->second;
- leftIndex.erase(right);
- leftIndex[leftKey] = index;
- geom[0].pop_back();
- geom[0].insert(geom[0].end(), features[index].geometry[0].begin(), features[index].geometry[0].end());
- features[index].geometry[0].clear();
- std::swap(features[index].geometry[0], geom[0]);
- return index;
-}
-
-std::string getKey(const std::u32string &text, const std::vector<std::vector<Coordinate>> &geom, bool onRight) {
- const Coordinate &coord = onRight ? geom[0].back() : geom[0].front();
- std::ostringstream key;
- for (const char32_t &c : text) {
- key << (char)c;
- }
- key << ":" << coord.x << ":" << coord.y;
- return key.str();
-}
-
-
-void mergeLines(std::vector<SymbolFeature> &features) {
-
- std::map<std::string,unsigned int> leftIndex;
- std::map<std::string,unsigned int> rightIndex;
-
- for (unsigned int k = 0; k < features.size(); k++) {
- SymbolFeature &feature = features[k];
- std::vector<std::vector<Coordinate>> &geometry = feature.geometry;
-
- if (!feature.label.length()) {
- continue;
- }
-
- std::string leftKey = getKey(feature.label, geometry, false);
- std::string rightKey = getKey(feature.label, geometry, true);
-
- auto left = rightIndex.find(leftKey);
- auto right = leftIndex.find(rightKey);
-
- if ((left != rightIndex.end()) && (right != leftIndex.end()) && (left->second != right->second)) {
- // found lines with the same text adjacent to both ends of the current line, merge all three
- unsigned int j = mergeFromLeft(features, leftIndex, leftKey, right, geometry);
- unsigned int i = mergeFromRight(features, rightIndex, left, rightKey, features[j].geometry);
-
- leftIndex.erase(leftKey);
- rightIndex.erase(rightKey);
- rightIndex[getKey(feature.label, features[i].geometry, true)] = i;
-
- } else if (left != rightIndex.end()) {
- // found mergeable line adjacent to the start of the current line, merge
- mergeFromRight(features, rightIndex, left, rightKey, geometry);
-
- } else if (right != leftIndex.end()) {
- // found mergeable line adjacent to the end of the current line, merge
- mergeFromLeft(features, leftIndex, leftKey, right, geometry);
-
- } else {
- // no adjacent lines, add as a new item
- leftIndex[leftKey] = k;
- rightIndex[rightKey] = k;
- }
+unsigned int mergeFromRight(std::vector<SymbolFeature> &features,
+ std::map<std::string, unsigned int> &rightIndex,
+ std::map<std::string, unsigned int>::iterator left,
+ std::string &rightKey,
+ std::vector<std::vector<Coordinate>> &geom);
- }
+unsigned int mergeFromLeft(std::vector<SymbolFeature> &features,
+ std::map<std::string, unsigned int> &leftIndex,
+ std::string &leftKey,
+ std::map<std::string, unsigned int>::iterator right,
+ std::vector<std::vector<Coordinate>> &geom);
-}
+void mergeLines(std::vector<SymbolFeature> &features);
} // end namespace util
} // end namespace mbgl
diff --git a/src/mbgl/util/uv-worker.c b/src/mbgl/util/uv-worker.c
index d2aa908019..ec8eae461c 100644
--- a/src/mbgl/util/uv-worker.c
+++ b/src/mbgl/util/uv-worker.c
@@ -25,7 +25,7 @@ void uv__worker_free_messenger(uv_messenger_t *msgr) {
void uv__worker_thread_finished(uv__worker_thread_t *worker_thread) {
uv_worker_t *worker = worker_thread->worker;
-#ifndef NDEBUG
+#ifdef DEBUG
assert(uv_thread_self() == worker->thread_id);
#endif
@@ -101,7 +101,7 @@ void uv__worker_thread_loop(void *ptr) {
}
int uv_worker_init(uv_worker_t *worker, uv_loop_t *loop, int count, const char *name) {
-#ifndef NDEBUG
+#ifdef DEBUG
worker->thread_id = uv_thread_self();
#endif
worker->loop = loop;
@@ -134,7 +134,7 @@ int uv_worker_init(uv_worker_t *worker, uv_loop_t *loop, int count, const char *
void uv_worker_send(uv_worker_t *worker, void *data, uv_worker_cb work_cb,
uv_worker_after_cb after_work_cb) {
-#ifndef NDEBUG
+#ifdef DEBUG
assert(uv_thread_self() == worker->thread_id);
#endif
@@ -155,7 +155,7 @@ void uv_worker_send(uv_worker_t *worker, void *data, uv_worker_cb work_cb,
}
void uv_worker_close(uv_worker_t *worker, uv_worker_close_cb close_cb) {
-#ifndef NDEBUG
+#ifdef DEBUG
assert(uv_thread_self() == worker->thread_id);
#endif
diff --git a/src/mbgl/util/uv-worker.h b/src/mbgl/util/uv-worker.h
index cb2075d1c3..5640bfd14d 100644
--- a/src/mbgl/util/uv-worker.h
+++ b/src/mbgl/util/uv-worker.h
@@ -17,7 +17,7 @@ typedef void (*uv_worker_after_cb)(void *data);
typedef void (*uv_worker_close_cb)(uv_worker_t *worker);
struct uv_worker_s {
-#ifndef NDEBUG
+#ifdef DEBUG
unsigned long thread_id;
#endif
uv_loop_t *loop;
diff --git a/src/mbgl/util/uv.cpp b/src/mbgl/util/uv.cpp
index 7aa5bad0cf..a993e6b962 100644
--- a/src/mbgl/util/uv.cpp
+++ b/src/mbgl/util/uv.cpp
@@ -22,4 +22,12 @@ std::string cwd() {
#endif
}
+const char *getFileRequestError(uv_fs_t *req) {
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+ return uv_strerror(uv_last_error(req->loop));
+#else
+ return uv_strerror(int(req->result));
+#endif
+}
+
}
diff --git a/test/fixtures/fixture_log.cpp b/test/fixtures/fixture_log.cpp
index 1b1646e665..02715fd43d 100644
--- a/test/fixtures/fixture_log.cpp
+++ b/test/fixtures/fixture_log.cpp
@@ -1,13 +1,59 @@
#include "fixture_log.hpp"
-#include <iostream>
-
namespace mbgl {
+FixtureLogBackend::LogMessage::LogMessage(EventSeverity severity_, Event event_, int64_t code_,
+ const std::string &msg_)
+ : severity(severity_), event(event_), code(code_), msg(msg_) {
+}
+FixtureLogBackend::LogMessage::LogMessage(EventSeverity severity_, Event event_, int64_t code_)
+ : severity(severity_), event(event_), code(code_), msg() {
+}
+FixtureLogBackend::LogMessage::LogMessage(EventSeverity severity_, Event event_,
+ const std::string &msg_)
+ : severity(severity_), event(event_), code(), msg(msg_) {
+}
+
+FixtureLogBackend::LogMessage::LogMessage(EventSeverity severity_, Event event_)
+ : severity(severity_), event(event_), code(), msg() {
+}
+
+bool FixtureLogBackend::LogMessage::operator==(const LogMessage &rhs) const {
+ return (!severity || !rhs.severity || severity.get() == rhs.severity.get()) &&
+ (!event || !rhs.event || event.get() == rhs.event.get()) &&
+ (!code || !rhs.code || code.get() == rhs.code.get()) &&
+ (!msg || !rhs.msg || msg.get() == rhs.msg.get());
+}
+
FixtureLogBackend::~FixtureLogBackend() {
std::cerr << unchecked();
}
+void FixtureLogBackend::record(EventSeverity severity, Event event, const std::string &msg) {
+ messages.emplace_back(severity, event, msg);
+}
+
+void FixtureLogBackend::record(EventSeverity severity, Event event, const char *format, ...) {
+ va_list args;
+ va_start(args, format);
+ const size_t len = vsnprintf(NULL, 0, format, args);
+ va_end(args);
+ std::unique_ptr<char[]> buffer(new char[len + 1]);
+ va_start(args, format);
+ vsnprintf(buffer.get(), len + 1, format, args);
+ va_end(args);
+ messages.emplace_back(severity, event, std::string{ buffer.get(), len });
+}
+
+void FixtureLogBackend::record(EventSeverity severity, Event event, int64_t code) {
+ messages.emplace_back(severity, event, code);
+}
+
+void FixtureLogBackend::record(EventSeverity severity, Event event, int64_t code,
+ const std::string &msg) {
+ messages.emplace_back(severity, event, code, msg);
+}
+
size_t FixtureLogBackend::count(const LogMessage &message) const {
size_t message_count = 0;
for (const LogMessage &msg : messages) {
@@ -30,18 +76,21 @@ std::vector<FixtureLogBackend::LogMessage> FixtureLogBackend::unchecked() const
return unchecked_messages;
}
-::std::ostream& operator<<(::std::ostream& os, const std::vector<FixtureLogBackend::LogMessage>& messages) {
+::std::ostream &operator<<(::std::ostream &os,
+ const std::vector<FixtureLogBackend::LogMessage> &messages) {
for (const FixtureLogBackend::LogMessage &message : messages) {
os << "- " << message;
}
return os;
}
-::std::ostream& operator<<(::std::ostream& os, const FixtureLogBackend::LogMessage& message) {
+::std::ostream &operator<<(::std::ostream &os, const FixtureLogBackend::LogMessage &message) {
os << "[\"" << message.severity.get() << "\", \"" << message.event.get() << "\"";
- if (message.code) os << ", " << message.code.get();
- if (message.msg) os << ", \"" << message.msg.get() << "\"";
+ if (message.code)
+ os << ", " << message.code.get();
+ if (message.msg)
+ os << ", \"" << message.msg.get() << "\"";
return os << "]" << std::endl;
}
-}
+} \ No newline at end of file
diff --git a/test/fixtures/fixture_log.hpp b/test/fixtures/fixture_log.hpp
index 99097a2c47..bc7c9fab31 100644
--- a/test/fixtures/fixture_log.hpp
+++ b/test/fixtures/fixture_log.hpp
@@ -7,27 +7,19 @@
#include <vector>
#include <cstdarg>
+#include <iostream>
namespace mbgl {
class FixtureLogBackend : public LogBackend, private util::noncopyable {
public:
struct LogMessage {
- inline LogMessage(EventSeverity severity_, Event event_, int64_t code_, const std::string &msg_)
- : severity(severity_), event(event_), code(code_), msg(msg_) {}
- inline LogMessage(EventSeverity severity_, Event event_, int64_t code_)
- : severity(severity_), event(event_), code(code_), msg() {}
- inline LogMessage(EventSeverity severity_, Event event_, const std::string &msg_)
- : severity(severity_), event(event_), code(), msg(msg_) {}
- inline LogMessage(EventSeverity severity_, Event event_)
- : severity(severity_), event(event_), code(), msg() {}
+ LogMessage(EventSeverity severity_, Event event_, int64_t code_, const std::string &msg_);
+ LogMessage(EventSeverity severity_, Event event_, int64_t code_);
+ LogMessage(EventSeverity severity_, Event event_, const std::string &msg_);
+ LogMessage(EventSeverity severity_, Event event_);
- inline bool operator==(const LogMessage &rhs) const {
- return (!severity || !rhs.severity || severity.get() == rhs.severity.get()) &&
- (!event || !rhs.event || event.get() == rhs.event.get()) &&
- (!code || !rhs.code || code.get() == rhs.code.get()) &&
- (!msg || !rhs.msg || msg.get() == rhs.msg.get());
- }
+ bool operator==(const LogMessage &rhs) const;
const mapbox::util::optional<EventSeverity> severity;
const mapbox::util::optional<Event> event;
@@ -39,29 +31,10 @@ public:
~FixtureLogBackend();
- void record(EventSeverity severity, Event event, const std::string &msg) {
- messages.emplace_back(severity, event, msg);
- }
-
- void record(EventSeverity severity, Event event, const char* format, ...) {
- va_list args;
- va_start(args, format);
- const size_t len = vsnprintf(NULL, 0, format, args);
- va_end(args);
- std::unique_ptr<char[]> buffer(new char[len + 1]);
- va_start(args, format);
- vsnprintf(buffer.get(), len + 1, format, args);
- va_end(args);
- messages.emplace_back(severity, event, std::string { buffer.get(), len });
- }
-
- void record(EventSeverity severity, Event event, int64_t code) {
- messages.emplace_back(severity, event, code);
- }
-
- void record(EventSeverity severity, Event event, int64_t code, const std::string &msg) {
- messages.emplace_back(severity, event, code, msg);
- }
+ inline void record(EventSeverity severity, Event event, const std::string &msg);
+ inline void record(EventSeverity severity, Event event, const char *format, ...);
+ inline void record(EventSeverity severity, Event event, int64_t code);
+ inline void record(EventSeverity severity, Event event, int64_t code, const std::string &msg);
size_t count(const LogMessage &message) const;
std::vector<LogMessage> unchecked() const;
@@ -70,9 +43,9 @@ public:
std::vector<LogMessage> messages;
};
-::std::ostream& operator<<(::std::ostream& os, const std::vector<FixtureLogBackend::LogMessage>& messages);
-::std::ostream& operator<<(::std::ostream& os, const FixtureLogBackend::LogMessage& message);
-
+::std::ostream &operator<<(::std::ostream &os,
+ const std::vector<FixtureLogBackend::LogMessage> &messages);
+::std::ostream &operator<<(::std::ostream &os, const FixtureLogBackend::LogMessage &message);
}
diff --git a/test/main.cpp b/test/fixtures/main.cpp
index ec813bde80..f7b2a6e92f 100644
--- a/test/main.cpp
+++ b/test/fixtures/main.cpp
@@ -1,4 +1,4 @@
-#include "gtest/gtest.h"
+#include "util.hpp"
GTEST_API_ int main(int argc, char *argv[]) {
testing::InitGoogleTest(&argc, argv);
diff --git a/test/fixtures/storage/empty b/test/fixtures/storage/empty
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/test/fixtures/storage/empty
diff --git a/test/fixtures/util.cpp b/test/fixtures/util.cpp
new file mode 100644
index 0000000000..7434393556
--- /dev/null
+++ b/test/fixtures/util.cpp
@@ -0,0 +1,35 @@
+#include "util.hpp"
+
+#include <csignal>
+
+namespace mbgl {
+namespace test {
+
+pid_t startServer(const char *executable) {
+ const std::string parent_pid = std::to_string(getpid());
+ pid_t pid = fork();
+ if (pid < 0) {
+ throw std::runtime_error("Cannot create server process");
+ } else if (pid == 0) {
+ char *args[] = { const_cast<char *>(executable),
+ const_cast<char *>(parent_pid.c_str()),
+ nullptr };
+ int ret = execv(executable, args);
+ // This call should not return. In case execve failed, we exit anyway.
+ if (ret < 0) {
+ fprintf(stderr, "Failed to start server: %s\n", strerror(errno));
+ }
+ exit(0);
+ } else {
+ // Wait until the server process sends SIGCONT.
+ raise(SIGSTOP);
+ }
+ return pid;
+}
+
+void stopServer(pid_t pid) {
+ kill(pid, SIGTERM);
+}
+
+}
+}
diff --git a/test/fixtures/util.hpp b/test/fixtures/util.hpp
new file mode 100644
index 0000000000..dd8539c846
--- /dev/null
+++ b/test/fixtures/util.hpp
@@ -0,0 +1,23 @@
+#ifndef MBGL_TEST_UTIL
+#define MBGL_TEST_UTIL
+
+#include <gtest/gtest.h>
+
+#define SCOPED_TEST(name) \
+ static class name { \
+ bool completed = false; \
+ public: \
+ void finish() { EXPECT_FALSE(completed) << #name " was already completed."; completed = true; } \
+ ~name() { if (!completed) ADD_FAILURE() << #name " didn't complete."; } \
+ } name;
+
+namespace mbgl {
+namespace test {
+
+pid_t startServer(const char *executable);
+void stopServer(pid_t pid);
+
+}
+}
+
+#endif
diff --git a/test/headless.cpp b/test/headless/headless.cpp
index 7d18ca451e..e7e1153923 100644
--- a/test/headless.cpp
+++ b/test/headless/headless.cpp
@@ -1,4 +1,5 @@
-#include "gtest/gtest.h"
+#include "../fixtures/util.hpp"
+#include "../fixtures/fixture_log.hpp"
#include <mbgl/map/map.hpp>
#include <mbgl/util/image.hpp>
@@ -9,85 +10,12 @@
#include <rapidjson/writer.h>
#include <rapidjson/stringbuffer.h>
+#include <mbgl/platform/platform.hpp>
#include <mbgl/platform/default/headless_view.hpp>
#include <mbgl/platform/default/headless_display.hpp>
-#include <mbgl/storage/caching_http_file_source.hpp>
-
-#include "./fixtures/fixture_log.hpp"
+#include <mbgl/storage/default_file_source.hpp>
#include <dirent.h>
-#include <signal.h>
-#include <libgen.h>
-#ifdef __linux__
-#include <sys/prctl.h>
-#endif
-
-std::string base_directory;
-
-namespace mbgl {
-namespace platform {
-
-std::string defaultCacheDatabase() {
- // Disables the cache.
- return "";
-}
-}
-}
-
-class ServerEnvironment : public ::testing::Environment {
-public:
- virtual void SetUp() {
- pid = fork();
- if (pid < 0) {
- throw std::runtime_error("Cannot create web server");
- } else if (pid == 0) {
-#ifdef __linux__
- prctl(PR_SET_PDEATHSIG, SIGHUP);
-#endif
- const auto executable = base_directory + "bin/server.py";
- const char *port = "2900";
- char *arg[] = { const_cast<char *>(executable.c_str()), const_cast<char *>(port), nullptr };
- int ret = execv(executable.c_str(), arg);
- // This call should not return. In case execve failed, we exit anyway.
- if (ret < 0) {
- fprintf(stderr, "Failed to start server: %s\n", strerror(errno));
- }
- exit(0);
- } else {
- display = std::make_shared<mbgl::HeadlessDisplay>();
- }
- }
- virtual void TearDown() {
- ASSERT_TRUE(pid);
- kill(pid, SIGHUP);
- }
-
- std::shared_ptr<mbgl::HeadlessDisplay> display;
-
-private:
- pid_t pid = 0;
-};
-
-
-ServerEnvironment* env = nullptr;
-
-
-GTEST_API_ int main(int argc, char *argv[]) {
- // Note: glibc's dirname() **modifies** the argument and can't handle static strings.
- std::string file { __FILE__ }; file = dirname(const_cast<char *>(file.c_str()));
- if (file[0] == '/') {
- // If __FILE__ is an absolute path, we don't have to guess from the argv 0.
- base_directory = file + "/suite/";
- } else {
- std::string argv0 { argv[0] }; argv0 = dirname(const_cast<char *>(argv0.c_str()));
- base_directory = argv0 + "/" + file + "/suite/";
- }
-
- testing::InitGoogleTest(&argc, argv);
- env = new ServerEnvironment();
- ::testing::AddGlobalTestEnvironment(env);
- return RUN_ALL_TESTS();
-}
void rewriteLocalScheme(rapidjson::Value &value, rapidjson::Document::AllocatorType &allocator) {
ASSERT_TRUE(value.IsString());
@@ -98,15 +26,35 @@ void rewriteLocalScheme(rapidjson::Value &value, rapidjson::Document::AllocatorT
}
}
-class HeadlessTest : public ::testing::TestWithParam<std::string> {};
+
+class HeadlessTest : public ::testing::TestWithParam<std::string> {
+public:
+ static void SetUpTestCase() {
+ const auto server = mbgl::platform::applicationRoot() + "/TEST_DATA/headless/server.js";
+ pid = mbgl::test::startServer(server.c_str());
+ display = std::make_shared<mbgl::HeadlessDisplay>();
+ }
+
+ static void TearDownTestCase() {
+ display.reset();
+ mbgl::test::stopServer(pid);
+ }
+
+protected:
+ static pid_t pid;
+ static std::shared_ptr<mbgl::HeadlessDisplay> display;
+};
+
+pid_t HeadlessTest::pid = 0;
+std::shared_ptr<mbgl::HeadlessDisplay> HeadlessTest::display;
TEST_P(HeadlessTest, render) {
using namespace mbgl;
const std::string& base = GetParam();
- std::string style = util::read_file(base_directory + "tests/" + base + "/style.json");
- std::string info = util::read_file(base_directory + "tests/" + base + "/info.json");
+ std::string style = util::read_file("test/suite/tests/" + base + "/style.json");
+ std::string info = util::read_file("test/suite/tests/" + base + "/info.json");
// Parse style.
rapidjson::Document styleDoc;
@@ -169,7 +117,7 @@ TEST_P(HeadlessTest, render) {
if (value.HasMember("center")) ASSERT_TRUE(value["center"].IsArray());
- const std::string actual_image = base_directory + "tests/" + base + "/" + name + "/actual.png";
+ const std::string actual_image = "test/suite/tests/" + base + "/" + name + "/actual.png";
const double zoom = value.HasMember("zoom") ? value["zoom"].GetDouble() : 0;
const double bearing = value.HasMember("bearing") ? value["bearing"].GetDouble() : 0;
@@ -190,19 +138,18 @@ TEST_P(HeadlessTest, render) {
}
}
- HeadlessView view(env->display);
- CachingHTTPFileSource fileSource(platform::defaultCacheDatabase());
+ HeadlessView view(display);
+ mbgl::DefaultFileSource fileSource(nullptr);
Map map(view, fileSource);
map.setClasses(classes);
- map.setStyleJSON(style, base_directory);
+ map.setStyleJSON(style, "test/suite");
view.resize(width, height, pixelRatio);
map.resize(width, height, pixelRatio);
map.setLonLatZoom(longitude, latitude, zoom);
map.setBearing(bearing);
-
// Run the loop. It will terminate when we don't have any further listeners.
map.run();
@@ -219,19 +166,18 @@ TEST_P(HeadlessTest, render) {
INSTANTIATE_TEST_CASE_P(Headless, HeadlessTest, ::testing::ValuesIn([] {
std::vector<std::string> names;
- DIR *dir = opendir((base_directory + "tests").c_str());
- if (dir == nullptr) {
- return names;
- }
-
- for (dirent *dp = nullptr; (dp = readdir(dir)) != nullptr;) {
- const std::string name = dp->d_name;
- if (name != "index.html" && !(name.size() >= 1 && name[0] == '.')) {
- names.push_back(name);
+ const auto tests = mbgl::platform::applicationRoot() + "/TEST_DATA/suite/tests";
+ DIR *dir = opendir(tests.c_str());
+ if (dir != nullptr) {
+ for (dirent *dp = nullptr; (dp = readdir(dir)) != nullptr;) {
+ const std::string name = dp->d_name;
+ if (name != "index.html" && !(name.size() >= 1 && name[0] == '.')) {
+ names.push_back(name);
+ }
}
+ closedir(dir);
}
- closedir(dir);
-
+ EXPECT_GT(names.size(), 0ul);
return names;
}()));
diff --git a/test/headless/server.js b/test/headless/server.js
new file mode 100755
index 0000000000..bb2c451f84
--- /dev/null
+++ b/test/headless/server.js
@@ -0,0 +1,19 @@
+#!/usr/bin/env node
+/* jshint node: true */
+'use strict';
+
+var express = require('express');
+var app = express();
+
+app.use(express.static('test/suite'));
+
+var server = app.listen(2900, function () {
+ var host = server.address().address;
+ var port = server.address().port;
+ console.log('Test server listening at http://%s:%s', host, port);
+
+ if (process.argv[2]) {
+ // Allow the test to continue running.
+ process.kill(+process.argv[2], 'SIGCONT');
+ }
+});
diff --git a/test/clip_ids.cpp b/test/miscellaneous/clip_ids.cpp
index 18ef9658e5..845b094520 100644
--- a/test/clip_ids.cpp
+++ b/test/miscellaneous/clip_ids.cpp
@@ -1,5 +1,5 @@
#include <iostream>
-#include "gtest/gtest.h"
+#include "../fixtures/util.hpp"
#include <algorithm>
diff --git a/test/comparisons.cpp b/test/miscellaneous/comparisons.cpp
index 9b74d6e36f..315416c135 100644
--- a/test/comparisons.cpp
+++ b/test/miscellaneous/comparisons.cpp
@@ -1,5 +1,5 @@
#include <iostream>
-#include "gtest/gtest.h"
+#include "../fixtures/util.hpp"
#include <mbgl/map/vector_tile.hpp>
#include <mbgl/style/filter_expression.hpp>
diff --git a/test/enums.cpp b/test/miscellaneous/enums.cpp
index b45fc0ed0d..f96b568f5b 100644
--- a/test/enums.cpp
+++ b/test/miscellaneous/enums.cpp
@@ -1,5 +1,5 @@
#include <iostream>
-#include "gtest/gtest.h"
+#include "../fixtures/util.hpp"
#include <algorithm>
diff --git a/test/functions.cpp b/test/miscellaneous/functions.cpp
index 56b2a31706..a82152fae6 100644
--- a/test/functions.cpp
+++ b/test/miscellaneous/functions.cpp
@@ -1,5 +1,5 @@
#include <iostream>
-#include "gtest/gtest.h"
+#include "../fixtures/util.hpp"
#include <mbgl/style/function_properties.hpp>
diff --git a/test/merge_lines.cpp b/test/miscellaneous/merge_lines.cpp
index 3c887105fa..0e7328d195 100644
--- a/test/merge_lines.cpp
+++ b/test/miscellaneous/merge_lines.cpp
@@ -1,13 +1,11 @@
-#include "gtest/gtest.h"
+#include "../fixtures/util.hpp"
#include <mbgl/util/merge_lines.hpp>
+const std::u32string a = U"a";
+const std::u32string b = U"b";
-TEST(mergeLines, mergeLines) {
-
- std::u32string a = U"a";
- std::u32string b = U"b";
-
+TEST(MergeLines, SameText) {
// merges lines with the same text
std::vector<mbgl::SymbolFeature> input1 = {
{ {{{0, 0}, {1, 0}, {2, 0}}}, a, "" },
@@ -18,7 +16,7 @@ TEST(mergeLines, mergeLines) {
{ {{{5, 0}, {6, 0}}}, a, "" }
};
- std::vector<mbgl::SymbolFeature> expected1 = {
+ const std::vector<mbgl::SymbolFeature> expected1 = {
{ {{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}}}, a, "" },
{ {{{4, 0}, {5, 0}, {6, 0}}}, b, "" },
{ {{{5, 0}, {6, 0}, {7, 0}, {8, 0}, {9, 0}}}, a, "" },
@@ -32,7 +30,9 @@ TEST(mergeLines, mergeLines) {
for (int i = 0; i < 6; i++) {
EXPECT_EQ(input1[i].geometry, expected1[i].geometry);
}
+}
+TEST(MergeLines, BothEnds) {
// mergeLines handles merge from both ends
std::vector<mbgl::SymbolFeature> input2 = {
{ {{{0, 0}, {1, 0}, {2, 0}}}, a, "" },
@@ -40,7 +40,7 @@ TEST(mergeLines, mergeLines) {
{ {{{2, 0}, {3, 0}, {4, 0}}}, a, "" }
};
- std::vector<mbgl::SymbolFeature> expected2 = {
+ const std::vector<mbgl::SymbolFeature> expected2 = {
{ {{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}}}, a, "" },
{ {{}}, a, "" },
{ {{}}, a, "" }
@@ -51,7 +51,9 @@ TEST(mergeLines, mergeLines) {
for (int i = 0; i < 3; i++) {
EXPECT_EQ(input2[i].geometry, expected2[i].geometry);
}
+}
+TEST(MergeLines, CircularLines) {
// mergeLines handles circular lines
std::vector<mbgl::SymbolFeature> input3 = {
{ {{{0, 0}, {1, 0}, {2, 0}}}, a, "" },
@@ -59,7 +61,7 @@ TEST(mergeLines, mergeLines) {
{ {{{4, 0}, {0, 0}}}, a, "" }
};
- std::vector<mbgl::SymbolFeature> expected3 = {
+ const std::vector<mbgl::SymbolFeature> expected3 = {
{ {{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {0, 0}}}, a, "" },
{ {{}}, a, "" },
{ {{}}, a, "" }
diff --git a/test/rotation_range.cpp b/test/miscellaneous/rotation_range.cpp
index 987d422686..3345dc0a9a 100644
--- a/test/rotation_range.cpp
+++ b/test/miscellaneous/rotation_range.cpp
@@ -1,5 +1,5 @@
#include <iostream>
-#include "gtest/gtest.h"
+#include "../fixtures/util.hpp"
#include <algorithm>
diff --git a/test/style_parser.cpp b/test/miscellaneous/style_parser.cpp
index fe237b7888..2cb3056204 100644
--- a/test/style_parser.cpp
+++ b/test/miscellaneous/style_parser.cpp
@@ -1,23 +1,17 @@
-#include "gtest/gtest.h"
+#include "../fixtures/util.hpp"
#include <mbgl/style/style.hpp>
#include <mbgl/util/io.hpp>
#include <rapidjson/document.h>
-#include "./fixtures/fixture_log.hpp"
+#include "../fixtures/fixture_log.hpp"
#include <iostream>
#include <fstream>
#include <dirent.h>
-const std::string base_directory = []{
- std::string fn = __FILE__;
- fn.erase(fn.find_last_of("/"));
- return fn + "/fixtures/style_parser";
-}();
-
using namespace mbgl;
typedef std::pair<uint32_t, std::string> Message;
@@ -26,7 +20,7 @@ typedef std::vector<Message> Messages;
class StyleParserTest : public ::testing::TestWithParam<std::string> {};
TEST_P(StyleParserTest, ParseStyle) {
- const std::string &base = base_directory + "/" + GetParam();
+ const std::string &base = "test/fixtures/style_parser/" + GetParam();
const std::string style_path = base + ".style.json";
const std::string info = util::read_file(base + ".info.json");
@@ -83,19 +77,18 @@ INSTANTIATE_TEST_CASE_P(StyleParser, StyleParserTest, ::testing::ValuesIn([] {
std::vector<std::string> names;
const std::string ending = ".info.json";
- DIR *dir = opendir(base_directory.c_str());
- if (dir == nullptr) {
- return names;
- }
-
- for (dirent *dp = nullptr; (dp = readdir(dir)) != nullptr;) {
- const std::string name = dp->d_name;
- if (name.length() >= ending.length() && name.compare(name.length() - ending.length(), ending.length(), ending) == 0) {
- names.push_back(name.substr(0, name.length() - ending.length()));
+ const std::string style_directory = "test/fixtures/style_parser";
+ DIR *dir = opendir(style_directory.c_str());
+ if (dir != nullptr) {
+ for (dirent *dp = nullptr; (dp = readdir(dir)) != nullptr;) {
+ const std::string name = dp->d_name;
+ if (name.length() >= ending.length() && name.compare(name.length() - ending.length(), ending.length(), ending) == 0) {
+ names.push_back(name.substr(0, name.length() - ending.length()));
+ }
}
+ closedir(dir);
}
- closedir(dir);
-
+ EXPECT_GT(names.size(), 0ul);
return names;
}()));
diff --git a/test/text_conversions.cpp b/test/miscellaneous/text_conversions.cpp
index 756bc4db1f..78d88ed12e 100644
--- a/test/text_conversions.cpp
+++ b/test/miscellaneous/text_conversions.cpp
@@ -1,5 +1,5 @@
#include <iostream>
-#include "gtest/gtest.h"
+#include "../fixtures/util.hpp"
#include <mbgl/util/utf.hpp>
#include <mbgl/platform/platform.hpp>
diff --git a/test/tile.cpp b/test/miscellaneous/tile.cpp
index 8a580f55a9..01da68f10d 100644
--- a/test/tile.cpp
+++ b/test/miscellaneous/tile.cpp
@@ -1,5 +1,5 @@
#include <iostream>
-#include "gtest/gtest.h"
+#include "../fixtures/util.hpp"
#include <mbgl/map/tile.hpp>
diff --git a/test/variant.cpp b/test/miscellaneous/variant.cpp
index e4caba9f2d..c567c61e90 100644
--- a/test/variant.cpp
+++ b/test/miscellaneous/variant.cpp
@@ -1,5 +1,5 @@
#include <iostream>
-#include "gtest/gtest.h"
+#include "../fixtures/util.hpp"
#include <mbgl/style/value.hpp>
#include <mbgl/style/value_comparison.hpp>
diff --git a/test/storage/cache_response.cpp b/test/storage/cache_response.cpp
new file mode 100644
index 0000000000..a0b5ba31c1
--- /dev/null
+++ b/test/storage/cache_response.cpp
@@ -0,0 +1,39 @@
+#include "storage.hpp"
+
+#include <uv.h>
+
+#include <mbgl/storage/default_file_source.hpp>
+#include <mbgl/storage/default/sqlite_cache.hpp>
+
+TEST_F(Storage, CacheResponse) {
+ SCOPED_TEST(CacheResponse);
+
+ using namespace mbgl;
+
+ SQLiteCache cache(":memory:");
+ DefaultFileSource fs(&cache, uv_default_loop());
+
+ const Resource resource { Resource::Unknown, "http://127.0.0.1:3000/cache" };
+
+ fs.request(resource, uv_default_loop(), [&](const Response &res) {
+ EXPECT_EQ(Response::Successful, res.status);
+ EXPECT_EQ("Response 1", res.data);
+ EXPECT_LT(0, res.expires);
+ EXPECT_EQ(0, res.modified);
+ EXPECT_EQ("", res.etag);
+ EXPECT_EQ("", res.message);
+
+ fs.request(resource, uv_default_loop(), [&, res](const Response &res2) {
+ EXPECT_EQ(res.status, res2.status);
+ EXPECT_EQ(res.data, res2.data);
+ EXPECT_EQ(res.expires, res2.expires);
+ EXPECT_EQ(res.modified, res2.modified);
+ EXPECT_EQ(res.etag, res2.etag);
+ EXPECT_EQ(res.message, res2.message);
+
+ CacheResponse.finish();
+ });
+ });
+
+ uv_run(uv_default_loop(), UV_RUN_DEFAULT);
+}
diff --git a/test/storage/cache_revalidate.cpp b/test/storage/cache_revalidate.cpp
new file mode 100644
index 0000000000..530b7325b5
--- /dev/null
+++ b/test/storage/cache_revalidate.cpp
@@ -0,0 +1,85 @@
+#include "storage.hpp"
+
+#include <uv.h>
+
+#include <mbgl/storage/default_file_source.hpp>
+#include <mbgl/storage/default/sqlite_cache.hpp>
+
+TEST_F(Storage, CacheRevalidate) {
+ SCOPED_TEST(CacheRevalidateSame)
+ SCOPED_TEST(CacheRevalidateModified)
+ SCOPED_TEST(CacheRevalidateEtag)
+
+ using namespace mbgl;
+
+ SQLiteCache cache(":memory:");
+ DefaultFileSource fs(&cache);
+
+ const Resource revalidateSame { Resource::Unknown, "http://127.0.0.1:3000/revalidate-same" };
+ fs.request(revalidateSame, uv_default_loop(), [&](const Response &res) {
+ EXPECT_EQ(Response::Successful, res.status);
+ EXPECT_EQ("Response", res.data);
+ EXPECT_EQ(0, res.expires);
+ EXPECT_EQ(0, res.modified);
+ EXPECT_EQ("snowfall", res.etag);
+ EXPECT_EQ("", res.message);
+
+ fs.request(revalidateSame, uv_default_loop(), [&, res](const Response &res2) {
+ EXPECT_EQ(Response::Successful, res2.status);
+ EXPECT_EQ("Response", res2.data);
+ // We use this to indicate that a 304 reply came back.
+ EXPECT_LT(0, res2.expires);
+ EXPECT_EQ(0, res2.modified);
+ // We're not sending the ETag in the 304 reply, but it should still be there.
+ EXPECT_EQ("snowfall", res2.etag);
+ EXPECT_EQ("", res2.message);
+
+ CacheRevalidateSame.finish();
+ });
+ });
+
+ const Resource revalidateModified { Resource::Unknown, "http://127.0.0.1:3000/revalidate-modified" };
+ fs.request(revalidateModified, uv_default_loop(), [&](const Response &res) {
+ EXPECT_EQ(Response::Successful, res.status);
+ EXPECT_EQ("Response", res.data);
+ EXPECT_EQ(0, res.expires);
+ EXPECT_EQ(1420070400, res.modified);
+ EXPECT_EQ("", res.etag);
+ EXPECT_EQ("", res.message);
+
+ fs.request(revalidateModified, uv_default_loop(), [&, res](const Response &res2) {
+ EXPECT_EQ(Response::Successful, res2.status);
+ EXPECT_EQ("Response", res2.data);
+ // We use this to indicate that a 304 reply came back.
+ EXPECT_LT(0, res2.expires);
+ EXPECT_EQ(1420070400, res2.modified);
+ EXPECT_EQ("", res2.etag);
+ EXPECT_EQ("", res2.message);
+
+ CacheRevalidateModified.finish();
+ });
+ });
+
+ const Resource revalidateEtag { Resource::Unknown, "http://127.0.0.1:3000/revalidate-etag" };
+ fs.request(revalidateEtag, uv_default_loop(), [&](const Response &res) {
+ EXPECT_EQ(Response::Successful, res.status);
+ EXPECT_EQ("Response 1", res.data);
+ EXPECT_EQ(0, res.expires);
+ EXPECT_EQ(0, res.modified);
+ EXPECT_EQ("response-1", res.etag);
+ EXPECT_EQ("", res.message);
+
+ fs.request(revalidateEtag, uv_default_loop(), [&, res](const Response &res2) {
+ EXPECT_EQ(Response::Successful, res2.status);
+ EXPECT_EQ("Response 2", res2.data);
+ EXPECT_EQ(0, res2.expires);
+ EXPECT_EQ(0, res2.modified);
+ EXPECT_EQ("response-2", res2.etag);
+ EXPECT_EQ("", res2.message);
+
+ CacheRevalidateEtag.finish();
+ });
+ });
+
+ uv_run(uv_default_loop(), UV_RUN_DEFAULT);
+}
diff --git a/test/storage/directory_reading.cpp b/test/storage/directory_reading.cpp
new file mode 100644
index 0000000000..bf472b5c79
--- /dev/null
+++ b/test/storage/directory_reading.cpp
@@ -0,0 +1,25 @@
+#include "storage.hpp"
+
+#include <uv.h>
+
+#include <mbgl/storage/default_file_source.hpp>
+
+TEST_F(Storage, ReadDirectory) {
+ SCOPED_TEST(ReadDirectory)
+
+ using namespace mbgl;
+
+ DefaultFileSource fs(nullptr, uv_default_loop());
+
+ fs.request({ Resource::Unknown, "asset://TEST_DATA/fixtures/storage" }, uv_default_loop(), [&](const Response &res) {
+ EXPECT_EQ(Response::Error, res.status);
+ EXPECT_EQ(0ul, res.data.size());
+ EXPECT_EQ(0, res.expires);
+ EXPECT_EQ(0, res.modified);
+ EXPECT_EQ("", res.etag);
+ EXPECT_EQ("illegal operation on a directory", res.message);
+ ReadDirectory.finish();
+ });
+
+ uv_run(uv_default_loop(), UV_RUN_DEFAULT);
+}
diff --git a/test/storage/file_reading.cpp b/test/storage/file_reading.cpp
new file mode 100644
index 0000000000..622eb2b8e8
--- /dev/null
+++ b/test/storage/file_reading.cpp
@@ -0,0 +1,50 @@
+#include "storage.hpp"
+
+#include <uv.h>
+
+#include <mbgl/storage/default_file_source.hpp>
+#include <mbgl/platform/platform.hpp>
+
+TEST_F(Storage, EmptyFile) {
+ SCOPED_TEST(EmptyFile)
+
+ using namespace mbgl;
+
+ DefaultFileSource fs(nullptr, uv_default_loop());
+
+ fs.request({ Resource::Unknown, "asset://TEST_DATA/fixtures/storage/empty" },
+ uv_default_loop(),
+ [&](const Response &res) {
+ EXPECT_EQ(Response::Successful, res.status);
+ EXPECT_EQ(0ul, res.data.size());
+ EXPECT_EQ(0, res.expires);
+ EXPECT_LT(1420000000, res.modified);
+ EXPECT_NE("", res.etag);
+ EXPECT_EQ("", res.message);
+ EmptyFile.finish();
+ });
+
+ uv_run(uv_default_loop(), UV_RUN_DEFAULT);
+}
+
+TEST_F(Storage, NonExistentFile) {
+ SCOPED_TEST(NonExistentFile)
+
+ using namespace mbgl;
+
+ DefaultFileSource fs(nullptr, uv_default_loop());
+
+ fs.request({ Resource::Unknown, "asset://TEST_DATA/fixtures/storage/does_not_exist" },
+ uv_default_loop(),
+ [&](const Response &res) {
+ EXPECT_EQ(Response::Error, res.status);
+ EXPECT_EQ(0ul, res.data.size());
+ EXPECT_EQ(0, res.expires);
+ EXPECT_EQ(0, res.modified);
+ EXPECT_EQ("", res.etag);
+ EXPECT_EQ("no such file or directory", res.message);
+ NonExistentFile.finish();
+ });
+
+ uv_run(uv_default_loop(), UV_RUN_DEFAULT);
+}
diff --git a/test/storage/http_cancel.cpp b/test/storage/http_cancel.cpp
new file mode 100644
index 0000000000..7e485ec42d
--- /dev/null
+++ b/test/storage/http_cancel.cpp
@@ -0,0 +1,50 @@
+#include "storage.hpp"
+
+#include <uv.h>
+
+#include <mbgl/storage/default_file_source.hpp>
+#include <mbgl/storage/network_status.hpp>
+
+#include <cmath>
+
+TEST_F(Storage, HTTPCancel) {
+ SCOPED_TEST(HTTPCancel)
+
+ using namespace mbgl;
+
+ DefaultFileSource fs(nullptr, uv_default_loop());
+
+ auto req = fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test" }, uv_default_loop(), [&](const Response &) {
+ ADD_FAILURE() << "Callback should not be called";
+ });
+
+ fs.cancel(req);
+ HTTPCancel.finish();
+
+ uv_run(uv_default_loop(), UV_RUN_DEFAULT);
+}
+
+TEST_F(Storage, HTTPCancelMultiple) {
+ SCOPED_TEST(HTTPCancelMultiple)
+
+ using namespace mbgl;
+
+ DefaultFileSource fs(nullptr, uv_default_loop());
+
+ const Resource resource { Resource::Unknown, "http://127.0.0.1:3000/test" };
+ auto req2 = fs.request(resource, uv_default_loop(), [&](const Response &) {
+ ADD_FAILURE() << "Callback should not be called";
+ });
+ fs.request(resource, uv_default_loop(), [&](const Response &res) {
+ EXPECT_EQ(Response::Successful, res.status);
+ EXPECT_EQ("Hello World!", res.data);
+ EXPECT_EQ(0, res.expires);
+ EXPECT_EQ(0, res.modified);
+ EXPECT_EQ("", res.etag);
+ EXPECT_EQ("", res.message);
+ HTTPCancelMultiple.finish();
+ });
+ fs.cancel(req2);
+
+ uv_run(uv_default_loop(), UV_RUN_DEFAULT);
+}
diff --git a/test/storage/http_coalescing.cpp b/test/storage/http_coalescing.cpp
new file mode 100644
index 0000000000..592c65f8d6
--- /dev/null
+++ b/test/storage/http_coalescing.cpp
@@ -0,0 +1,48 @@
+#include "storage.hpp"
+
+#include <uv.h>
+
+#include <mbgl/storage/default_file_source.hpp>
+
+TEST_F(Storage, HTTPCoalescing) {
+ SCOPED_TEST(HTTPCoalescing)
+
+ static int counter = 0;
+ const static int total = 4;
+
+ using namespace mbgl;
+
+ DefaultFileSource fs(nullptr, uv_default_loop());
+
+ static const Response *reference = nullptr;
+
+ const auto complete = [&](const Response &res) {
+ counter++;
+
+ // Make sure all of the Response objects are the same.
+ if (!reference) {
+ reference = &res;
+ } else {
+ EXPECT_EQ(&res, reference);
+ }
+
+ EXPECT_EQ(Response::Successful, res.status);
+ EXPECT_EQ("Hello World!", res.data);
+ EXPECT_EQ(0, res.expires);
+ EXPECT_EQ(0, res.modified);
+ EXPECT_EQ("", res.etag);
+ EXPECT_EQ("", res.message);
+
+ if (counter >= total) {
+ HTTPCoalescing.finish();
+ }
+ };
+
+ const Resource resource { Resource::Unknown, "http://127.0.0.1:3000/test" };
+
+ for (int i = 0; i < total; i++) {
+ fs.request(resource, uv_default_loop(), complete);
+ }
+
+ uv_run(uv_default_loop(), UV_RUN_DEFAULT);
+}
diff --git a/test/storage/http_error.cpp b/test/storage/http_error.cpp
new file mode 100644
index 0000000000..45c23b94b6
--- /dev/null
+++ b/test/storage/http_error.cpp
@@ -0,0 +1,63 @@
+#include "storage.hpp"
+
+#include <uv.h>
+
+#include <mbgl/storage/default_file_source.hpp>
+#include <mbgl/storage/network_status.hpp>
+
+#include <cmath>
+
+TEST_F(Storage, HTTPError) {
+ SCOPED_TEST(HTTPTemporaryError)
+ SCOPED_TEST(HTTPConnectionError)
+
+ using namespace mbgl;
+
+ uv_timer_t statusChange;
+ uv_timer_init(uv_default_loop(), &statusChange);
+ uv_timer_start(&statusChange, [](uv_timer_t *, int) {
+ NetworkStatus::Reachable();
+ }, 500, 500);
+ uv_unref((uv_handle_t *)&statusChange);
+
+ DefaultFileSource fs(nullptr, uv_default_loop());
+
+ auto start = uv_hrtime();
+
+ fs.request({ Resource::Unknown, "http://127.0.0.1:3000/temporary-error" }, uv_default_loop(), [&](const Response &res) {
+ const auto duration = double(uv_hrtime() - start) / 1e9;
+ EXPECT_LT(1, duration) << "Backoff timer didn't wait 1 second";
+ EXPECT_GT(1.2, duration) << "Backoff timer fired too late";
+ EXPECT_EQ(Response::Successful, res.status);
+ EXPECT_EQ("Hello World!", res.data);
+ EXPECT_EQ(0, res.expires);
+ EXPECT_EQ(0, res.modified);
+ EXPECT_EQ("", res.etag);
+ EXPECT_EQ("", res.message);
+
+ HTTPTemporaryError.finish();
+ });
+
+ fs.request({ Resource::Unknown, "http://127.0.0.1:3001/" }, uv_default_loop(), [&](const Response &res) {
+ const auto duration = double(uv_hrtime() - start) / 1e9;
+ // 1.5 seconds == 4 retries, with a 500ms timeout (see above).
+ EXPECT_LT(1.5, duration) << "Resource wasn't retried the correct number of times";
+ EXPECT_GT(1.7, duration) << "Resource wasn't retried the correct number of times";
+ EXPECT_EQ(Response::Error, res.status);
+ EXPECT_TRUE(res.message == "Couldn't connect to server" || res.message == "The operation couldn’t be completed. (NSURLErrorDomain error -1004.)");
+ EXPECT_EQ("", res.data);
+ EXPECT_EQ(0, res.expires);
+ EXPECT_EQ(0, res.modified);
+ EXPECT_EQ("", res.etag);
+ HTTPConnectionError.finish();
+ });
+
+
+ uv_run(uv_default_loop(), UV_RUN_DEFAULT);
+
+ uv_timer_stop(&statusChange);
+ uv_close(reinterpret_cast<uv_handle_t *>(&statusChange), nullptr);
+
+ // Run again so that the timer handle can be properly closed.
+ uv_run(uv_default_loop(), UV_RUN_DEFAULT);
+}
diff --git a/test/storage/http_header_parsing.cpp b/test/storage/http_header_parsing.cpp
new file mode 100644
index 0000000000..655348c877
--- /dev/null
+++ b/test/storage/http_header_parsing.cpp
@@ -0,0 +1,42 @@
+#include "storage.hpp"
+
+#include <uv.h>
+
+#include <mbgl/storage/default_file_source.hpp>
+
+#include <cmath>
+
+TEST_F(Storage, HTTPHeaderParsing) {
+ SCOPED_TEST(HTTPExpiresTest)
+ SCOPED_TEST(HTTPCacheControlTest)
+
+ using namespace mbgl;
+
+ DefaultFileSource fs(nullptr, uv_default_loop());
+
+ fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test?modified=1420794326&expires=1420797926&etag=foo" }, uv_default_loop(), [&](const Response &res) {
+ EXPECT_EQ(Response::Successful, res.status);
+ EXPECT_EQ("Hello World!", res.data);
+ EXPECT_EQ(1420797926, res.expires);
+ EXPECT_EQ(1420794326, res.modified);
+ EXPECT_EQ("foo", res.etag);
+ EXPECT_EQ("", res.message);
+ HTTPExpiresTest.finish();
+ });
+
+
+ int64_t now = std::chrono::duration_cast<std::chrono::seconds>(
+ std::chrono::system_clock::now().time_since_epoch()).count();
+
+ fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test?cachecontrol=max-age=120" }, uv_default_loop(), [&](const Response &res) {
+ EXPECT_EQ(Response::Successful, res.status);
+ EXPECT_EQ("Hello World!", res.data);
+ EXPECT_GT(2, std::abs(res.expires - now - 120)) << "Expiration date isn't about 120 seconds in the future";
+ EXPECT_EQ(0, res.modified);
+ EXPECT_EQ("", res.etag);
+ EXPECT_EQ("", res.message);
+ HTTPCacheControlTest.finish();
+ });
+
+ uv_run(uv_default_loop(), UV_RUN_DEFAULT);
+}
diff --git a/test/storage/http_load.cpp b/test/storage/http_load.cpp
new file mode 100644
index 0000000000..fff662da29
--- /dev/null
+++ b/test/storage/http_load.cpp
@@ -0,0 +1,42 @@
+#include "storage.hpp"
+
+#include <uv.h>
+
+#include <mbgl/storage/default_file_source.hpp>
+
+TEST_F(Storage, HTTPLoad) {
+ SCOPED_TEST(HTTPLoad)
+
+ using namespace mbgl;
+
+ DefaultFileSource fs(nullptr, uv_default_loop());
+
+ const int concurrency = 50;
+ const int max = 10000;
+ int number = 1;
+
+ std::function<void()> req = [&]() {
+ const auto current = number++;
+ fs.request({ Resource::Unknown, std::string("http://127.0.0.1:3000/load/") + std::to_string(current) }, uv_default_loop(), [&, current](const Response &res) {
+ EXPECT_EQ(Response::Successful, res.status);
+ EXPECT_EQ(std::string("Request ") + std::to_string(current), res.data);
+ EXPECT_EQ(0, res.expires);
+ EXPECT_EQ(0, res.modified);
+ EXPECT_EQ("", res.etag);
+ EXPECT_EQ("", res.message);
+
+ if (number <= max) {
+ req();
+ } else if (current == max) {
+ HTTPLoad.finish();
+ }
+ });
+ };
+
+
+ for (int i = 0; i < concurrency; i++) {
+ req();
+ }
+
+ uv_run(uv_default_loop(), UV_RUN_DEFAULT);
+}
diff --git a/test/storage/http_noloop.cpp b/test/storage/http_noloop.cpp
new file mode 100644
index 0000000000..c71fd0bc26
--- /dev/null
+++ b/test/storage/http_noloop.cpp
@@ -0,0 +1,37 @@
+#include "storage.hpp"
+
+#include <uv.h>
+
+#include <mbgl/storage/default_file_source.hpp>
+#include <mbgl/util/uv.hpp>
+
+TEST_F(Storage, HTTPNoLoop) {
+ SCOPED_TEST(HTTPNoLoop)
+
+ using namespace mbgl;
+
+ DefaultFileSource fs(nullptr);
+
+ const auto mainThread = uv_thread_self();
+
+ // Async handle that keeps the main loop alive until the thread finished
+ auto async = new uv_async_t;
+ uv_async_init(uv_default_loop(), async, [] (uv_async_t *as, int) {
+ uv::close(as);
+ });
+
+ fs.request({ Resource::Unknown, "http://127.0.0.1:3000/temporary-error" }, [&](const Response &res) {
+ EXPECT_NE(uv_thread_self(), mainThread) << "Response was called in the same thread";
+ EXPECT_EQ(Response::Successful, res.status);
+ EXPECT_EQ("Hello World!", res.data);
+ EXPECT_EQ(0, res.expires);
+ EXPECT_EQ(0, res.modified);
+ EXPECT_EQ("", res.etag);
+ EXPECT_EQ("", res.message);
+ HTTPNoLoop.finish();
+
+ uv_async_send(async);
+ });
+
+ uv_run(uv_default_loop(), UV_RUN_DEFAULT);
+}
diff --git a/test/storage/http_other_loop.cpp b/test/storage/http_other_loop.cpp
new file mode 100644
index 0000000000..06cc6f7476
--- /dev/null
+++ b/test/storage/http_other_loop.cpp
@@ -0,0 +1,26 @@
+#include "storage.hpp"
+
+#include <uv.h>
+
+#include <mbgl/storage/default_file_source.hpp>
+
+TEST_F(Storage, HTTPOtherLoop) {
+ SCOPED_TEST(HTTPOtherLoop)
+
+ using namespace mbgl;
+
+ // This file source launches a separate thread to do the processing.
+ DefaultFileSource fs(nullptr);
+
+ fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test" }, uv_default_loop(), [&](const Response &res) {
+ EXPECT_EQ(Response::Successful, res.status);
+ EXPECT_EQ("Hello World!", res.data);
+ EXPECT_EQ(0, res.expires);
+ EXPECT_EQ(0, res.modified);
+ EXPECT_EQ("", res.etag);
+ EXPECT_EQ("", res.message);
+ HTTPOtherLoop.finish();
+ });
+
+ uv_run(uv_default_loop(), UV_RUN_DEFAULT);
+}
diff --git a/test/storage/http_reading.cpp b/test/storage/http_reading.cpp
new file mode 100644
index 0000000000..3aa57cd805
--- /dev/null
+++ b/test/storage/http_reading.cpp
@@ -0,0 +1,39 @@
+#include "storage.hpp"
+
+#include <uv.h>
+
+#include <mbgl/storage/default_file_source.hpp>
+
+TEST_F(Storage, HTTPReading) {
+ SCOPED_TEST(HTTPTest)
+ SCOPED_TEST(HTTP404)
+
+ using namespace mbgl;
+
+ DefaultFileSource fs(nullptr, uv_default_loop());
+
+ const auto mainThread = uv_thread_self();
+
+ fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test" }, uv_default_loop(), [&](const Response &res) {
+ EXPECT_EQ(uv_thread_self(), mainThread);
+ EXPECT_EQ(Response::Successful, res.status);
+ EXPECT_EQ("Hello World!", res.data);
+ EXPECT_EQ(0, res.expires);
+ EXPECT_EQ(0, res.modified);
+ EXPECT_EQ("", res.etag);
+ EXPECT_EQ("", res.message);
+ HTTPTest.finish();
+ });
+
+ fs.request({ Resource::Unknown, "http://127.0.0.1:3000/doesnotexist" }, uv_default_loop(), [&](const Response &res) {
+ EXPECT_EQ(uv_thread_self(), mainThread);
+ EXPECT_EQ(Response::Error, res.status);
+ EXPECT_EQ("HTTP status code 404", res.message);
+ EXPECT_EQ(0, res.expires);
+ EXPECT_EQ(0, res.modified);
+ EXPECT_EQ("", res.etag);
+ HTTP404.finish();
+ });
+
+ uv_run(uv_default_loop(), UV_RUN_DEFAULT);
+}
diff --git a/test/storage/server.js b/test/storage/server.js
new file mode 100755
index 0000000000..33d5a82985
--- /dev/null
+++ b/test/storage/server.js
@@ -0,0 +1,101 @@
+#!/usr/bin/env node
+/* jshint node: true */
+'use strict';
+
+var express = require('express');
+var app = express();
+
+// We're manually setting Etag headers.
+app.disable('etag');
+
+app.get('/test', function (req, res) {
+ if (req.query.modified) {
+ res.setHeader('Last-Modified', (new Date(req.query.modified * 1000)).toUTCString());
+ }
+ if (req.query.expires) {
+ res.setHeader('Expires', (new Date(req.query.expires * 1000)).toUTCString());
+ }
+ if (req.query.etag) {
+ res.setHeader('ETag', req.query.etag);
+ }
+ if (req.query.cachecontrol) {
+ res.setHeader('Cache-Control', req.query.cachecontrol);
+ }
+ res.send('Hello World!');
+});
+
+
+var cacheCounter = 0;
+app.get('/cache', function(req, res) {
+ res.setHeader('Cache-Control', 'max-age=30'); // Allow caching for 30 seconds
+ res.send('Response ' + (++cacheCounter));
+});
+
+app.get('/revalidate-same', function(req, res) {
+ if (req.headers['if-none-match'] == 'snowfall') {
+ // Second request can be cached for 30 seconds.
+ res.setHeader('Cache-Control', 'max-age=30');
+ res.status(304).end();
+ } else {
+ // First request must always be revalidated.
+ res.setHeader('ETag', 'snowfall');
+ res.setHeader('Cache-Control', 'must-revalidate');
+ res.status(200).send('Response');
+ }
+});
+
+
+app.get('/revalidate-modified', function(req, res) {
+ var jan1 = new Date('jan 1 2015 utc');
+
+ if (req.headers['if-modified-since']) {
+ var modified_since = new Date(req.headers['if-modified-since']);
+ if (modified_since >= jan1) {
+ res.setHeader('Cache-Control', 'max-age=30');
+ res.status(304).end();
+ return;
+ }
+ }
+
+ // First request must always be revalidated.
+ res.setHeader('Last-Modified', jan1.toUTCString());
+ res.setHeader('Cache-Control', 'must-revalidate');
+ res.status(200).send('Response');
+});
+
+
+var revalidateEtagCounter = 1;
+app.get('/revalidate-etag', function(req, res) {
+ res.setHeader('ETag', 'response-' + revalidateEtagCounter);
+ res.setHeader('Cache-Control', 'must-revalidate');
+
+ res.status(200).send('Response ' + revalidateEtagCounter);
+ revalidateEtagCounter++;
+});
+
+
+var temporaryErrorCounter = 0;
+app.get('/temporary-error', function(req, res) {
+ if (temporaryErrorCounter === 0) {
+ res.status(500).end();
+ } else {
+ res.status(200).send('Hello World!');
+ }
+
+ temporaryErrorCounter++;
+});
+
+app.get('/load/:number(\\d+)', function(req, res) {
+ res.send('Request ' + req.params.number);
+});
+
+var server = app.listen(3000, function () {
+ var host = server.address().address;
+ var port = server.address().port;
+ console.warn('Storage test server listening at http://%s:%s', host, port);
+
+ if (process.argv[2]) {
+ // Allow the test to continue running.
+ process.kill(+process.argv[2], 'SIGCONT');
+ }
+});
diff --git a/test/storage/storage.cpp b/test/storage/storage.cpp
new file mode 100644
index 0000000000..ddc6fef5fb
--- /dev/null
+++ b/test/storage/storage.cpp
@@ -0,0 +1,14 @@
+#include "storage.hpp"
+
+#include <mbgl/platform/platform.hpp>
+
+pid_t Storage::pid = 0;
+
+void Storage::SetUpTestCase() {
+ const auto server = mbgl::platform::applicationRoot() + "/TEST_DATA/storage/server.js";
+ pid = mbgl::test::startServer(server.c_str());
+}
+
+void Storage::TearDownTestCase() {
+ mbgl::test::stopServer(pid);
+} \ No newline at end of file
diff --git a/test/storage/storage.hpp b/test/storage/storage.hpp
new file mode 100644
index 0000000000..d2402166a0
--- /dev/null
+++ b/test/storage/storage.hpp
@@ -0,0 +1,16 @@
+#ifndef MBGL_TEST_STORAGE_STORAGE
+#define MBGL_TEST_STORAGE_STORAGE
+
+#include "../fixtures/util.hpp"
+#include <uv.h>
+
+class Storage : public testing::Test {
+public:
+ static void SetUpTestCase();
+ static void TearDownTestCase();
+
+protected:
+ static pid_t pid;
+};
+
+#endif
diff --git a/test/test.gyp b/test/test.gyp
index 406e0b23a7..1d80a4f830 100644
--- a/test/test.gyp
+++ b/test/test.gyp
@@ -1,267 +1,99 @@
{
'includes': [
'../gyp/common.gypi',
- '../gyp/version.gypi',
- '../gyp/mbgl-platform.gypi',
],
- 'variables' : {
- 'ldflags': [
- '<@(uv_ldflags)',
- '<@(sqlite3_static_libs)',
- '<@(sqlite3_ldflags)',
- '<@(curl_ldflags)',
- '<@(png_ldflags)',
- ],
- },
'targets': [
- { 'target_name': 'rotation_range',
- 'product_name': 'test_rotation_range',
- 'type': 'executable',
- 'sources': [
- './main.cpp',
- './rotation_range.cpp',
- ],
- 'dependencies': [
- '../deps/gtest/gtest.gyp:gtest',
- '../mapboxgl.gyp:mbgl-standalone',
- ],
- 'include_dirs': [ '../src' ],
- 'conditions': [
- ['OS == "mac"', { 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)' ] }
- }, {
- 'libraries': [ '<@(ldflags)' ],
- }]
- ]
- },
- { 'target_name': 'clip_ids',
- 'product_name': 'test_clip_ids',
- 'type': 'executable',
- 'sources': [
- './main.cpp',
- './clip_ids.cpp',
- ],
- 'dependencies': [
- '../deps/gtest/gtest.gyp:gtest',
- '../mapboxgl.gyp:mbgl-standalone',
- ],
- 'include_dirs': [ '../src' ],
- 'conditions': [
- ['OS == "mac"', { 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)'] }
- }, {
- 'libraries': [ '<@(ldflags)' ],
- }]
- ]
- },
- { 'target_name': 'enums',
- 'product_name': 'test_enums',
- 'type': 'executable',
- 'sources': [
- './main.cpp',
- './enums.cpp',
- ],
- 'dependencies': [
- '../deps/gtest/gtest.gyp:gtest',
- '../mapboxgl.gyp:mbgl-standalone',
- ],
- 'include_dirs': [ '../src' ],
- 'conditions': [
- ['OS == "mac"', { 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)'] }
- }, {
- 'libraries': [ '<@(ldflags)' ],
- }]
- ]
- },
- { 'target_name': 'style_parser',
- 'product_name': 'test_style_parser',
- 'type': 'executable',
- 'sources': [
- './main.cpp',
- './style_parser.cpp',
- './fixtures/fixture_log.hpp',
- './fixtures/fixture_log.cpp',
- ],
- 'dependencies': [
- '../deps/gtest/gtest.gyp:gtest',
- '../mapboxgl.gyp:mbgl-standalone'
- ],
- 'include_dirs': [ '../src' ],
- 'conditions': [
- ['OS == "mac"', { 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)' ] }
- }, {
- 'libraries': [ '<@(ldflags)' ],
- }]
- ]
- },
- { 'target_name': 'variant',
- 'product_name': 'test_variant',
- 'type': 'executable',
- 'sources': [
- './main.cpp',
- './variant.cpp',
- ],
- 'dependencies': [
- '../deps/gtest/gtest.gyp:gtest',
- '../mapboxgl.gyp:mbgl-standalone',
- ],
- 'include_dirs': [ '../src' ],
- 'conditions': [
- ['OS == "mac"', { 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)' ] }
- }, {
- 'libraries': [ '<@(ldflags)' ],
- }]
- ]
- },
- { 'target_name': 'comparisons',
- 'product_name': 'test_comparisons',
- 'type': 'executable',
- 'sources': [
- './main.cpp',
- './comparisons.cpp',
- ],
- 'dependencies': [
- '../deps/gtest/gtest.gyp:gtest',
- '../mapboxgl.gyp:mbgl-standalone',
- ],
- 'include_dirs': [ '../src' ],
- 'conditions': [
- ['OS == "mac"', { 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)' ] }
- }, {
- 'libraries': [ '<@(ldflags)' ],
- }]
- ]
- },
- { 'target_name': 'tile',
- 'product_name': 'test_tile',
- 'type': 'executable',
- 'sources': [
- './main.cpp',
- './tile.cpp',
- ],
- 'dependencies': [
- '../deps/gtest/gtest.gyp:gtest',
- '../mapboxgl.gyp:mbgl-standalone',
- ],
- 'include_dirs': [ '../src' ],
- 'conditions': [
- ['OS == "mac"', { 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)' ] }
- }, {
- 'libraries': [ '<@(ldflags)' ],
- }]
- ]
- },
- { 'target_name': 'functions',
- 'product_name': 'test_functions',
- 'type': 'executable',
- 'sources': [
- './main.cpp',
- './functions.cpp',
- ],
- 'dependencies': [
- '../deps/gtest/gtest.gyp:gtest',
- '../mapboxgl.gyp:mbgl-standalone',
- ],
- 'include_dirs': [ '../src' ],
- 'conditions': [
- ['OS == "mac"', { 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)' ] }
- }, {
- 'libraries': [ '<@(ldflags)' ],
- }]
- ]
- },
- { 'target_name': 'merge_lines',
- 'product_name': 'test_merge_lines',
- 'type': 'executable',
- 'sources': [
- './main.cpp',
- './merge_lines.cpp',
- ],
- 'dependencies': [
- '../deps/gtest/gtest.gyp:gtest',
- '../mapboxgl.gyp:mbgl-standalone',
+ { 'target_name': 'symlink_TEST_DATA',
+ 'type': 'none',
+ 'hard_dependency': 1,
+ 'actions': [
+ {
+ 'action_name': 'Symlink Fixture Directory',
+ 'inputs': ['<!@(pwd)'],
+ 'outputs': ['<(PRODUCT_DIR)/TEST_DATA'], # symlinks the test dir into TEST_DATA
+ 'action': ['ln', '-s', '-f', '-n', '<@(_inputs)', '<@(_outputs)' ],
+ }
],
- 'include_dirs': [ '../src' ],
- 'conditions': [
- ['OS == "mac"', { 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)' ] }
- }, {
- 'libraries': [ '<@(ldflags)' ],
- }]
- ]
},
- { 'target_name': 'headless',
- 'product_name': 'test_headless',
+ { 'target_name': 'test',
'type': 'executable',
- 'sources': [
- './headless.cpp',
- './fixtures/fixture_log.cpp',
- ],
- 'conditions': [
- # add libuv include path and OpenGL libs
- ['OS == "mac"',
- {
- 'xcode_settings': {
- 'OTHER_CPLUSPLUSFLAGS': ['<@(uv_cflags)','<@(png_cflags)'],
- 'OTHER_LDFLAGS': ['<@(glfw3_ldflags)', '<@(ldflags)'],
- },
- },
- {
- 'cflags': ['<@(uv_cflags)','<@(png_cflags)'],
- 'libraries': ['<@(glfw3_ldflags)', '<@(ldflags)'],
- }],
- ],
+ 'include_dirs': [ '../include', '../src' ],
'dependencies': [
- '../deps/gtest/gtest.gyp:gtest',
- '../mapboxgl.gyp:mbgl-standalone',
- '../mapboxgl.gyp:mbgl-headless',
- '<(platform_library)',
+ 'symlink_TEST_DATA',
+ '../mbgl.gyp:core',
+ '../mbgl.gyp:platform-<(platform_lib)',
+ '../mbgl.gyp:http-<(http_lib)',
+ '../mbgl.gyp:asset-<(asset_lib)',
+ '../mbgl.gyp:cache-<(cache_lib)',
+ '../mbgl.gyp:headless-<(headless_lib)',
+ '../deps/gtest/gtest.gyp:gtest'
],
- 'include_dirs': [ '../src' ]
- },
- { 'target_name': 'text_conversions',
- 'product_name': 'test_text_conversions',
- 'type': 'executable',
'sources': [
- './main.cpp',
- './text_conversions.cpp',
- ],
- 'dependencies': [
- '../deps/gtest/gtest.gyp:gtest',
- '../mapboxgl.gyp:mbgl-standalone',
- '<(platform_library)',
+ 'fixtures/main.cpp',
+ 'fixtures/util.hpp',
+ 'fixtures/util.cpp',
+ 'fixtures/fixture_log.hpp',
+ 'fixtures/fixture_log.cpp',
+
+ 'headless/headless.cpp',
+
+ 'miscellaneous/clip_ids.cpp',
+ 'miscellaneous/comparisons.cpp',
+ 'miscellaneous/enums.cpp',
+ 'miscellaneous/functions.cpp',
+ 'miscellaneous/merge_lines.cpp',
+ 'miscellaneous/rotation_range.cpp',
+ 'miscellaneous/style_parser.cpp',
+ 'miscellaneous/text_conversions.cpp',
+ 'miscellaneous/tile.cpp',
+ 'miscellaneous/variant.cpp',
+
+ 'storage/storage.hpp',
+ 'storage/storage.cpp',
+ 'storage/cache_response.cpp',
+ 'storage/cache_revalidate.cpp',
+ 'storage/directory_reading.cpp',
+ 'storage/file_reading.cpp',
+ 'storage/http_cancel.cpp',
+ 'storage/http_coalescing.cpp',
+ 'storage/http_error.cpp',
+ 'storage/http_header_parsing.cpp',
+ 'storage/http_load.cpp',
+ 'storage/http_noloop.cpp',
+ 'storage/http_other_loop.cpp',
+ 'storage/http_reading.cpp',
+ ],
+ 'libraries': [
+ '<@(uv_static_libs)',
+ '<@(glfw3_static_libs)',
+ '<@(sqlite3_static_libs)',
+ '<@(zlib_static_libs)',
+ '<@(curl_static_libs)',
],
- 'include_dirs': [ '../src' ],
'variables': {
'cflags_cc': [
- '-I<(boost_root)/include',
- ]
+ '<@(uv_cflags)',
+ '<@(boost_cflags)',
+ ],
+ 'ldflags': [
+ '<@(uv_ldflags)',
+ '<@(glfw3_ldflags)',
+ '<@(sqlite3_ldflags)',
+ '<@(zlib_ldflags)',
+ '<@(curl_ldflags)',
+ ],
},
'conditions': [
['OS == "mac"', {
'xcode_settings': {
'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ],
- 'OTHER_LDFLAGS': [ '<@(ldflags)', '-framework Foundation' ]
+ 'OTHER_LDFLAGS': [ '<@(ldflags)' ],
},
}, {
- 'cflags_cc': [ '<@(cflags_cc)' ],
- 'libraries': [ '<@(ldflags)'],
- }]
- ]
- },
- # Build all targets
- { 'target_name': 'test',
- 'type': 'none',
- 'dependencies': [
- 'rotation_range',
- 'clip_ids',
- 'enums',
- 'variant',
- 'tile',
- 'functions',
- 'merge_lines',
- 'headless',
- 'style_parser',
- 'comparisons',
- 'text_conversions',
+ 'cflags_cc': [ '<@(cflags_cc)' ],
+ 'libraries': [ '<@(ldflags)' ],
+ }],
],
- }
+ },
]
}
diff --git a/utils/mbgl-config/build.sh b/utils/mbgl-config/build.sh
index fe7cbdf60a..f5e6c5d482 100755
--- a/utils/mbgl-config/build.sh
+++ b/utils/mbgl-config/build.sh
@@ -1,17 +1,14 @@
#!/bin/bash
PREFIX=$1
-PLATFORM=$2
-shift
-shift
+LDFLAGS=$2
VARIABLES="#!/bin/bash
## variables
CONFIG_MBGL_PREFIX=$PREFIX
-CONFIG_MBGL_PLATFORM=$PLATFORM
-CONFIG_MBGL_LDFLAGS=\"$@\"
+CONFIG_MBGL_LDFLAGS=\"`cat $LDFLAGS`\"
"
echo "$VARIABLES" | cat - utils/mbgl-config/mbgl-config.template.sh \
diff --git a/utils/mbgl-config/mbgl-config.template.sh b/utils/mbgl-config/mbgl-config.template.sh
index 89e28f64a8..f51120ad18 100644
--- a/utils/mbgl-config/mbgl-config.template.sh
+++ b/utils/mbgl-config/mbgl-config.template.sh
@@ -24,12 +24,6 @@ if test $# -eq 0; then
usage 1
fi
-if [ ${CONFIG_MBGL_PLATFORM} == 'linux' ]; then
- LIBS="-Wl,--start-group -lmbgl-core -lmbgl-${CONFIG_MBGL_PLATFORM} -Wl,--end-group -lmbgl-headless"
-else
- LIBS="-lmbgl-core -lmbgl-${CONFIG_MBGL_PLATFORM} -lmbgl-headless"
-fi
-
while test $# -gt 0; do
case "$1" in
esac
@@ -49,7 +43,7 @@ while test $# -gt 0; do
;;
--libs)
- echo -L${CONFIG_MBGL_PREFIX}/lib ${LIBS} ${CONFIG_MBGL_LDFLAGS}
+ echo ${CONFIG_MBGL_PREFIX}/lib/libmbgl.a ${CONFIG_MBGL_LDFLAGS}
;;
--includedir)