From f0e4e27cd383f9c8b674cbfebb7a15ba8f8b6c0b Mon Sep 17 00:00:00 2001 From: Leith Bade Date: Mon, 17 Nov 2014 23:17:22 +1100 Subject: [PATCH] Read styles from APK --- android/cpp/jni.cpp | 6 +- .../arieslabs/assetbridge/Assetbridge.java | 2 +- .../java/com/mapbox/mapboxgl/lib/MapView.java | 5 +- .../mapbox/mapboxgl/lib/NativeMapView.java | 6 +- android/mapboxgl-app.gyp | 2 + include/mbgl/android/jni.hpp | 1 + .../platform/android/asset_request_baton.hpp | 18 +- platform/android/asset_request_baton.cpp | 212 +++++++++--------- src/storage/file_request_baton.cpp | 2 +- src/storage/file_source.cpp | 4 +- 10 files changed, 130 insertions(+), 128 deletions(-) diff --git a/android/cpp/jni.cpp b/android/cpp/jni.cpp index 06d9d4f8e..ec9283b87 100644 --- a/android/cpp/jni.cpp +++ b/android/cpp/jni.cpp @@ -27,6 +27,7 @@ namespace android { std::string cache_path; std::string data_path; +std::string apk_path; jmethodID on_map_changed_id = nullptr; @@ -142,10 +143,11 @@ namespace { using namespace mbgl::android; -jlong JNICALL nativeCreate(JNIEnv* env, jobject obj, jstring cache_path_, jstring data_path_) { +jlong JNICALL nativeCreate(JNIEnv* env, jobject obj, jstring cache_path_, jstring data_path_, jstring apk_path_) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeCreate"); cache_path = std_string_from_jstring(env, cache_path_); data_path = std_string_from_jstring(env, data_path_); + apk_path = std_string_from_jstring(env, apk_path_); NativeMapView* native_map_view = nullptr; COFFEE_TRY_JNI(env, native_map_view = new NativeMapView(env, obj)); if (native_map_view == nullptr) { @@ -787,7 +789,7 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { // NOTE: if you get java.lang.UnsatisfiedLinkError you likely forgot to set the size of the array correctly (too large) std::array methods = {{ // Can remove the extra brace in C++14 - { "nativeCreate", "(Ljava/lang/String;Ljava/lang/String;)J", reinterpret_cast(&nativeCreate) }, + { "nativeCreate", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)J", reinterpret_cast(&nativeCreate) }, { "nativeDestroy", "(J)V", reinterpret_cast(&nativeDestroy) }, { "nativeInitializeDisplay", "(J)V", reinterpret_cast(&nativeInitializeDisplay) }, { "nativeTerminateDisplay", "(J)V", reinterpret_cast(&nativeTerminateDisplay) }, diff --git a/android/java/lib/src/main/java/com/arieslabs/assetbridge/Assetbridge.java b/android/java/lib/src/main/java/com/arieslabs/assetbridge/Assetbridge.java index e32475931..357f053f6 100644 --- a/android/java/lib/src/main/java/com/arieslabs/assetbridge/Assetbridge.java +++ b/android/java/lib/src/main/java/com/arieslabs/assetbridge/Assetbridge.java @@ -33,7 +33,7 @@ public class Assetbridge { // only extract what we need ArrayList assets = new ArrayList(); assets.add("ca-bundle.crt"); - assets.add("styles"); + //assets.add("styles"); // iterate on the files... for(String asset : assets) { diff --git a/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/MapView.java b/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/MapView.java index 4ae6d991a..48fb8840d 100644 --- a/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/MapView.java +++ b/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/MapView.java @@ -141,13 +141,14 @@ public class MapView extends SurfaceView { // Get the cache path String cachePath = context.getCacheDir().getAbsolutePath(); String dataPath = context.getFilesDir().getAbsolutePath(); + String apkPath = context.getPackageCodePath(); // Extract the asset files Assetbridge.unpack(context); // Load the map style and API key //mStyleUrl = "https://mapbox.github.io/mapbox-gl-styles/styles/bright-v6.json"; - mStyleUrl = "file://" + dataPath + "/styles/styles/bright-v6.json"; + mStyleUrl = "asset://styles/styles/bright-v6.json"; mAccessToken = "pk.eyJ1IjoibGpiYWRlIiwiYSI6IlJSQ0FEZ2MifQ.7mE4aOegldh3595AG9dxpQ"; // Are we a debug build? @@ -155,7 +156,7 @@ public class MapView extends SurfaceView { ApplicationInfo.FLAG_DEBUGGABLE) == ApplicationInfo.FLAG_DEBUGGABLE; // Create the NativeMapView - mNativeMapView = new NativeMapView(this, cachePath, dataPath); + mNativeMapView = new NativeMapView(this, cachePath, dataPath, apkPath); mNativeMapView.setStyleURL(mStyleUrl); mNativeMapView.setAccessToken(mAccessToken); mNativeMapView.setDebug(isDebuggable); diff --git a/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/NativeMapView.java b/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/NativeMapView.java index e0e005f66..fc525a594 100644 --- a/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/NativeMapView.java +++ b/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/NativeMapView.java @@ -37,11 +37,11 @@ class NativeMapView { // Constructors // - public NativeMapView(MapView mapView, String cachePath, String dataPath) { + public NativeMapView(MapView mapView, String cachePath, String dataPath, String apkPath) { mMapView = mapView; // Create the NativeMapView - mNativeMapViewPtr = nativeCreate(cachePath, dataPath); + mNativeMapViewPtr = nativeCreate(cachePath, dataPath, apkPath); } // @@ -407,7 +407,7 @@ class NativeMapView { super.finalize(); } - private native long nativeCreate(String cachePath, String dataPath); + private native long nativeCreate(String cachePath, String dataPath, String apkPath); private native void nativeDestroy(long nativeMapViewPtr); diff --git a/android/mapboxgl-app.gyp b/android/mapboxgl-app.gyp index b01050f0b..1140dd5cb 100644 --- a/android/mapboxgl-app.gyp +++ b/android/mapboxgl-app.gyp @@ -23,6 +23,7 @@ '<@(sqlite3_static_libs)', '<@(uv_static_libs)', '<@(nu_static_libs)', + '<@(zip_static_libs)', ], 'variables': { 'ldflags': [ @@ -37,6 +38,7 @@ '<@(openssl_ldflags)', '<@(curl_ldflags)', '<@(zlib_ldflags)', + '<@(zip_ldflags)', ], }, 'conditions': [ diff --git a/include/mbgl/android/jni.hpp b/include/mbgl/android/jni.hpp index c1af07c64..28b9b8917 100644 --- a/include/mbgl/android/jni.hpp +++ b/include/mbgl/android/jni.hpp @@ -9,6 +9,7 @@ namespace android { extern std::string cache_path; extern std::string data_path; +extern std::string apk_path; extern jmethodID on_map_changed_id; diff --git a/include/mbgl/platform/android/asset_request_baton.hpp b/include/mbgl/platform/android/asset_request_baton.hpp index d8a0fbf3e..1acbfbae0 100644 --- a/include/mbgl/platform/android/asset_request_baton.hpp +++ b/include/mbgl/platform/android/asset_request_baton.hpp @@ -4,6 +4,7 @@ #include #include +#include namespace mbgl { @@ -12,20 +13,17 @@ struct AssetRequestBaton { ~AssetRequestBaton(); void cancel(); - static void file_opened(uv_fs_t *req); - static void file_stated(uv_fs_t *req); - static void file_read(uv_fs_t *req); - static void file_closed(uv_fs_t *req); - static void notify_error(uv_fs_t *req); - static void cleanup(uv_fs_t *req); + static void run(uv_async_t *async); + static void notify_error(uv_async_t *async, const int code, const char *message); + static void cleanup(uv_async_t *async); const unsigned long thread_id; AssetRequest *request = nullptr; - uv_fs_t req; - uv_file fd = -1; + std::unique_ptr async_run; + struct zip *apk = nullptr; + struct zip_file *apk_file = nullptr; + std::string path; bool canceled = false; - std::string body; - uv_buf_t buffer; }; diff --git a/platform/android/asset_request_baton.cpp b/platform/android/asset_request_baton.cpp index 8563312b4..c4e0a1799 100644 --- a/platform/android/asset_request_baton.cpp +++ b/platform/android/asset_request_baton.cpp @@ -1,158 +1,156 @@ +#include #include #include #include #include +#include namespace mbgl { -AssetRequestBaton::AssetRequestBaton(AssetRequest *request_, const std::string &path, uv_loop_t *loop) - : thread_id(uv_thread_self()), request(request_) { - req.data = this; - uv_fs_open(loop, &req, path.c_str(), O_RDONLY, S_IRUSR, file_opened); +AssetRequestBaton::AssetRequestBaton(AssetRequest *request_, const std::string &path_, uv_loop_t *loop) + : thread_id(uv_thread_self()), + request(request_), + async_run(new uv_async_t()), + path(path_) { + + uv_async_init(loop, async_run.get(), run); + async_run->data = this; + + uv_async_send(async_run.get()); } AssetRequestBaton::~AssetRequestBaton() { + uv_close((uv_handle_t *)async_run.get(), nullptr); } 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::notify_error(uv_fs_t *req) { - AssetRequestBaton *ptr = (AssetRequestBaton *)req->data; +void AssetRequestBaton::notify_error(uv_async_t *async, const int code, const char *message) { + AssetRequestBaton *ptr = (AssetRequestBaton *)async->data; assert(ptr->thread_id == uv_thread_self()); - if (ptr->request && req->result < 0 && !ptr->canceled && req->result != UV_ECANCELED) { + if (ptr->request && !ptr->canceled) { ptr->request->response = std::unique_ptr(new 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->response->code = code; + ptr->request->response->message = message; ptr->request->notify(); } } -void AssetRequestBaton::file_opened(uv_fs_t *req) { - AssetRequestBaton *ptr = (AssetRequestBaton *)req->data; +void AssetRequestBaton::run(uv_async_t *async) { + AssetRequestBaton *ptr = (AssetRequestBaton *)async->data; assert(ptr->thread_id == uv_thread_self()); - if (req->result < 0) { - // Opening failed or was canceled. There isn't much left we can do. - notify_error(req); - cleanup(req); - } else { - const uv_file fd = uv_file(req->result); - - // We're going to reuse this handle, so we need to cleanup first. - uv_fs_req_cleanup(req); - - if (ptr->canceled || !ptr->request) { - // Either the AssetRequest object has been destructed, or the - // request was canceled. - uv_fs_close(req->loop, req, fd, file_closed); - } else { - ptr->fd = fd; - uv_fs_fstat(req->loop, req, fd, file_stated); - } + if (ptr->canceled || !ptr->request) { + // Either the AssetRequest object has been destructed, or the + // request was canceled. + cleanup(async); + return; } -} -void AssetRequestBaton::file_stated(uv_fs_t *req) { - AssetRequestBaton *ptr = (AssetRequestBaton *)req->data; - assert(ptr->thread_id == uv_thread_self()); + int error = 0; + ptr->apk = zip_open(mbgl::android::apk_path.c_str(), 0, &error); + if (ptr->apk == nullptr || ptr->canceled || !ptr->request) { + // Opening the APK failed or was canceled. There isn't much left we can do. + const int message_size = zip_error_to_str(nullptr, 0, error, errno); + const std::unique_ptr message = boost::make_unique(message_size); + zip_error_to_str(message.get(), 0, error, errno); + notify_error(async, 500, message.get()); + cleanup(async); + return; + } - if (req->result != 0 || ptr->canceled || !ptr->request) { + std::string apk_file_path = "assets/" + ptr->path; + ptr->apk_file = zip_fopen(ptr->apk, apk_file_path.c_str(), ZIP_FL_NOCASE); + if (ptr->apk_file == 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(ptr->apk, &error, nullptr); + notify_error(async, error == ZIP_ER_NOENT ? 404 : 500, zip_strerror(ptr->apk)); + zip_close(ptr->apk); + ptr->apk = nullptr; + cleanup(async); + return; + } + + struct zip_stat stat; + if (zip_stat(ptr->apk, apk_file_path.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. - notify_error(req); - - uv_fs_req_cleanup(req); - uv_fs_close(req->loop, req, ptr->fd, file_closed); - } else { -#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 - const uv_statbuf_t *stat = static_cast(req->ptr); -#else - const uv_stat_t *stat = static_cast(req->ptr); -#endif - if (stat->st_size > std::numeric_limits::max()) { - // File is too large for us to open this way because uv_buf's only support unsigned - // ints as maximum size. - if (ptr->request) { - ptr->request->response = std::unique_ptr(new 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, file_closed); - } else { - const unsigned int size = (unsigned int)(stat->st_size); - ptr->body.resize(size); - ptr->buffer = uv_buf_init(const_cast(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, file_read); -#else - uv_fs_read(req->loop, req, ptr->fd, &ptr->buffer, 1, 0, file_read); -#endif - } + notify_error(async, 500, zip_strerror(ptr->apk)); + zip_fclose(ptr->apk_file); + ptr->apk_file = nullptr; + zip_close(ptr->apk); + ptr->apk = nullptr; + cleanup(async); + return; } -} - -void AssetRequestBaton::file_read(uv_fs_t *req) { - AssetRequestBaton *ptr = (AssetRequestBaton *)req->data; - assert(ptr->thread_id == uv_thread_self()); - - if (req->result < 0 || ptr->canceled || !ptr->request) { - // Stating failed or was canceled. We already have an open file handle - // though, which we'll have to close. - notify_error(req); - } else { - // File was successfully read. +/* + if (stat.size > std::numeric_limits::max()) { + // File is too large for us to open this way because uv_buf's only support unsigned + // ints as maximum size. if (ptr->request) { ptr->request->response = std::unique_ptr(new Response); - ptr->request->response->code = 200; - ptr->request->response->data = std::move(ptr->body); + ptr->request->response->code = UV_EFBIG; + ptr->request->response->message = uv_strerror(UV_EFBIG); ptr->request->notify(); } + zip_fclose(ptr->apk_file); + ptr->apk_file = nullptr; + zip_close(ptr->apk); + ptr->apk = nullptr; + cleanup(async); + return; + } +*/ + //const unsigned int size = static_cast(stat.size); + const std::unique_ptr body = boost::make_unique(stat.size); + + if (zip_fread(ptr->apk_file, reinterpret_cast(body.get()), stat.size) == -1 || + ptr->canceled || !ptr->request) { + // Reading failed or was canceled. We already have an open file handle + // though, which we'll have to close. + notify_error(async, 500, zip_file_strerror(ptr->apk_file)); + zip_fclose(ptr->apk_file); + ptr->apk_file = nullptr; + zip_close(ptr->apk); + ptr->apk = nullptr; + cleanup(async); + return; } - uv_fs_req_cleanup(req); - uv_fs_close(req->loop, req, ptr->fd, file_closed); -} - -void AssetRequestBaton::file_closed(uv_fs_t *req) { - assert(((AssetRequestBaton *)req->data)->thread_id == uv_thread_self()); - - if (req->result < 0) { - // Closing the file failed. But there isn't anything we can do. + if (ptr->request) { + ptr->request->response = std::unique_ptr(new Response); + ptr->request->response->code = 200; + ptr->request->response->data = body.get(); + ptr->request->notify(); } - cleanup(req); + if (zip_fclose(ptr->apk_file) != 0) { + // Closing the asset failed. But there isn't anything we can do. + } + ptr->apk_file = nullptr; + + if (zip_close(ptr->apk) != 0) { + // Closing the APK failed. But there isn't anything we can do. + } + ptr->apk = nullptr; + + cleanup(async); } -void AssetRequestBaton::cleanup(uv_fs_t *req) { - AssetRequestBaton *ptr = (AssetRequestBaton *)req->data; +void AssetRequestBaton::cleanup(uv_async_t *async) { + AssetRequestBaton *ptr = (AssetRequestBaton *)async->data; assert(ptr->thread_id == uv_thread_self()); if (ptr->request) { ptr->request->ptr = nullptr; } - uv_fs_req_cleanup(req); delete ptr; } diff --git a/src/storage/file_request_baton.cpp b/src/storage/file_request_baton.cpp index 1b41a5370..991853b28 100644 --- a/src/storage/file_request_baton.cpp +++ b/src/storage/file_request_baton.cpp @@ -117,7 +117,7 @@ void FileRequestBaton::file_read(uv_fs_t *req) { assert(ptr->thread_id == uv_thread_self()); if (req->result < 0 || ptr->canceled || !ptr->request) { - // Stating failed or was canceled. We already have an open file handle + // Reading failed or was canceled. We already have an open file handle // though, which we'll have to close. notify_error(req); } else { diff --git a/src/storage/file_source.cpp b/src/storage/file_source.cpp index fc71b8ea5..d8c7ee88b 100644 --- a/src/storage/file_source.cpp +++ b/src/storage/file_source.cpp @@ -77,8 +77,8 @@ std::unique_ptr FileSource::request(ResourceType type, const std::strin if (absoluteURL.substr(0, 7) == "file://") { req = std::make_shared(absoluteURL.substr(7), loop); #ifdef __ANDROID__ - } else if (absoluteURL.substr(0, 7) == "asset://") { - req = std::make_shared(absoluteURL.substr(7), loop); + } else if (absoluteURL.substr(0, 8) == "asset://") { + req = std::make_shared(absoluteURL.substr(8), loop); #endif } else { req = std::make_shared(type, absoluteURL, loop, store);