Files
MapLibreParticleLab/platform/default/asset_request_zip.cpp
John Firebaugh 8e5fa341eb Remove RequestBase::start
Requests are always started immediately upon construction.

While here, use typed pointers, not void*.
2015-04-28 09:16:26 -04:00

300 lines
8.3 KiB
C++

#include <mbgl/storage/asset_request.hpp>
#include <mbgl/storage/thread_context.hpp>
#include <mbgl/android/jni.hpp>
#include <mbgl/storage/resource.hpp>
#include <mbgl/storage/response.hpp>
#include <mbgl/platform/log.hpp>
#include <mbgl/util/std.hpp>
#include "uv_zip.h"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wshadow"
#ifndef __clang__
#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
#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(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<std::string, 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(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> 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<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;
}
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>();
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;
}
}