diff options
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: @@ -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 @@ -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)' ], + }] ], }, ], @@ -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) |