#include #include #include #include #include #include #include #include "uv_zip.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wshadow" #ifndef __clang__ #pragma GCC diagnostic ignored "-Wunused-local-typedefs" #endif #include #pragma GCC diagnostic pop #include namespace algo = boost::algorithm; namespace mbgl { class AssetZipContext : public ThreadContext { public: AssetZipContext(uv_loop_t *loop); ~AssetZipContext(); uv_zip_t *getHandle(const std::string &path); void returnHandle(const std::string &path, uv_zip_t *zip); private: // A list of resuable uv_zip handles to avoid creating and destroying them all the time. std::map> handles; }; // ------------------------------------------------------------------------------------------------- template<> pthread_key_t ThreadContext::key{}; template<> pthread_once_t ThreadContext::once = PTHREAD_ONCE_INIT; AssetZipContext::AssetZipContext(uv_loop_t *loop_) : ThreadContext(loop_) { } uv_zip_t *AssetZipContext::getHandle(const std::string &path) { auto &list = handles[path]; if (!list.empty()) { auto zip = list.front(); list.pop_front(); return zip; } else { return nullptr; } } void AssetZipContext::returnHandle(const std::string &path, uv_zip_t *zip) { handles[path].push_front(zip); } AssetZipContext::~AssetZipContext() { // Close all zip handles for (auto &list : handles) { for (auto zip : list.second) { 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*, uv_loop_t*, const std::string& assetRoot); ~AssetRequestImpl(); void cancel(); private: AssetZipContext &context; AssetRequest *request = nullptr; const std::string root; const std::string path; std::unique_ptr 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->impl = nullptr; } } AssetRequestImpl::AssetRequestImpl(AssetRequest* request_, uv_loop_t* loop, const std::string& assetRoot_) : context(*AssetZipContext::Get(loop)), request(request_), root(assetRoot_), path(std::string { "assets/" } + request->resource.url.substr(8)) { auto zip = context.getHandle(root); if (zip) { archiveOpened(zip); } else { openZipArchive(); } } void AssetRequestImpl::openZipArchive() { uv_fs_t *req = new uv_fs_t(); req->data = this; assert(request); // 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, root.c_str(), O_RDONLY, S_IRUSR, [](uv_fs_t *fsReq) { if (fsReq->result < 0) { auto impl = reinterpret_cast(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(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(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(); // Allocate the space for reading the data. response->data.resize(zip->stat->size); buffer = uv_buf_init(const_cast(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; } if (zip->stat->valid & ZIP_STAT_INDEX) { response->etag = std::to_string(zip->stat->index); } 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(root, zip); delete this; } void AssetRequestImpl::notifyError(const char *message) { MBGL_VERIFY_THREAD(tid); if (request) { response = util::make_unique(); 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(const Resource& resource_, Callback callback_, uv_loop_t* loop, const std::string& assetRoot) : RequestBase(resource_, callback_) , impl(new AssetRequestImpl(this, loop, assetRoot)) { assert(algo::starts_with(resource.url, "asset://")); } AssetRequest::~AssetRequest() { if (impl) { impl->cancel(); } } void AssetRequest::cancel() { if (impl) { impl->cancel(); } delete this; } }