Files
MapLibreParticleLab/platform/android/src/http_file_source.cpp
2016-09-29 18:40:10 +02:00

194 lines
6.4 KiB
C++

#include <mbgl/storage/http_file_source.hpp>
#include <mbgl/storage/resource.hpp>
#include <mbgl/storage/response.hpp>
#include <mbgl/platform/log.hpp>
#include <mbgl/util/async_task.hpp>
#include <mbgl/util/util.hpp>
#include <mbgl/util/string.hpp>
#include <mbgl/util/http_header.hpp>
#include <jni/jni.hpp>
#include "attach_env.hpp"
namespace mbgl {
class HTTPFileSource::Impl {
public:
android::UniqueEnv env { android::AttachEnv() };
};
class HTTPRequest : public AsyncRequest {
public:
static constexpr auto Name() { return "com/mapbox/mapboxsdk/http/HTTPRequest"; };
HTTPRequest(jni::JNIEnv&, const Resource&, FileSource::Callback);
~HTTPRequest();
void onFailure(jni::JNIEnv&, int type, jni::String message);
void onResponse(jni::JNIEnv&, int code,
jni::String etag, jni::String modified,
jni::String cacheControl, jni::String expires,
jni::String retryAfter, jni::String xRateLimitReset,
jni::Array<jni::jbyte> body);
static jni::Class<HTTPRequest> javaClass;
jni::UniqueObject<HTTPRequest> javaRequest;
private:
Resource resource;
FileSource::Callback callback;
Response response;
util::AsyncTask async { [this] {
// Calling `callback` may result in deleting `this`. Copy data to temporaries first.
auto callback_ = callback;
auto response_ = response;
callback_(response_);
} };
static const int connectionError = 0;
static const int temporaryError = 1;
static const int permanentError = 2;
};
jni::Class<HTTPRequest> HTTPRequest::javaClass;
namespace android {
void RegisterNativeHTTPRequest(jni::JNIEnv& env) {
HTTPRequest::javaClass = *jni::Class<HTTPRequest>::Find(env).NewGlobalRef(env).release();
#define METHOD(MethodPtr, name) jni::MakeNativePeerMethod<decltype(MethodPtr), (MethodPtr)>(name)
jni::RegisterNativePeer<HTTPRequest>(env, HTTPRequest::javaClass, "mNativePtr",
METHOD(&HTTPRequest::onFailure, "nativeOnFailure"),
METHOD(&HTTPRequest::onResponse, "nativeOnResponse"));
}
} // namespace android
HTTPRequest::HTTPRequest(jni::JNIEnv& env, const Resource& resource_, FileSource::Callback callback_)
: resource(resource_),
callback(callback_) {
std::string etagStr;
std::string modifiedStr;
if (resource.priorEtag) {
etagStr = *resource.priorEtag;
} else if (resource.priorModified) {
modifiedStr = util::rfc1123(*resource.priorModified);
}
jni::UniqueLocalFrame frame = jni::PushLocalFrame(env, 10);
static auto constructor =
javaClass.GetConstructor<jni::jlong, jni::String, jni::String, jni::String>(env);
javaRequest = javaClass.New(env, constructor,
reinterpret_cast<jlong>(this),
jni::Make<jni::String>(env, resource.url),
jni::Make<jni::String>(env, etagStr),
jni::Make<jni::String>(env, modifiedStr)).NewGlobalRef(env);
}
HTTPRequest::~HTTPRequest() {
android::UniqueEnv env = android::AttachEnv();
static auto cancel = javaClass.GetMethod<void ()>(*env, "cancel");
javaRequest->Call(*env, cancel);
}
void HTTPRequest::onResponse(jni::JNIEnv& env, int code,
jni::String etag, jni::String modified,
jni::String cacheControl, jni::String expires,
jni::String jRetryAfter, jni::String jXRateLimitReset,
jni::Array<jni::jbyte> body) {
using Error = Response::Error;
if (etag) {
response.etag = jni::Make<std::string>(env, etag);
}
if (modified) {
response.modified = util::parseTimestamp(jni::Make<std::string>(env, modified).c_str());
}
if (cacheControl) {
response.expires = http::CacheControl::parse(jni::Make<std::string>(env, cacheControl).c_str()).toTimePoint();
}
if (expires) {
response.expires = util::parseTimestamp(jni::Make<std::string>(env, expires).c_str());
}
if (code == 200) {
if (body) {
auto data = std::make_shared<std::string>(body.Length(env), char());
jni::GetArrayRegion(env, *body, 0, data->size(), reinterpret_cast<jbyte*>(&(*data)[0]));
response.data = data;
} else {
response.data = std::make_shared<std::string>();
}
} else if (code == 204 || (code == 404 && resource.kind == Resource::Kind::Tile)) {
response.noContent = true;
} else if (code == 304) {
response.notModified = true;
} else if (code == 404) {
response.error = std::make_unique<Error>(Error::Reason::NotFound, "HTTP status code 404");
} else if (code == 429) {
optional<std::string> retryAfter;
optional<std::string> xRateLimitReset;
if (jRetryAfter) {
retryAfter = jni::Make<std::string>(env, jRetryAfter);
}
if (jXRateLimitReset) {
xRateLimitReset = jni::Make<std::string>(env, jXRateLimitReset);
}
response.error = std::make_unique<Error>(Error::Reason::RateLimit, "HTTP status code 429", http::parseRetryHeaders(retryAfter, xRateLimitReset));
} else if (code >= 500 && code < 600) {
response.error = std::make_unique<Error>(Error::Reason::Server, std::string{ "HTTP status code " } + std::to_string(code));
} else {
response.error = std::make_unique<Error>(Error::Reason::Other, std::string{ "HTTP status code " } + std::to_string(code));
}
async.send();
}
void HTTPRequest::onFailure(jni::JNIEnv& env, int type, jni::String message) {
std::string messageStr = jni::Make<std::string>(env, message);
using Error = Response::Error;
switch (type) {
case connectionError:
response.error = std::make_unique<Error>(Error::Reason::Connection, messageStr);
break;
case temporaryError:
response.error = std::make_unique<Error>(Error::Reason::Server, messageStr);
break;
default:
response.error = std::make_unique<Error>(Error::Reason::Other, messageStr);
}
async.send();
}
HTTPFileSource::HTTPFileSource()
: impl(std::make_unique<Impl>()) {
}
HTTPFileSource::~HTTPFileSource() = default;
std::unique_ptr<AsyncRequest> HTTPFileSource::request(const Resource& resource, Callback callback) {
return std::make_unique<HTTPRequest>(*impl->env, resource, callback);
}
uint32_t HTTPFileSource::maximumConcurrentRequests() {
return 20;
}
} // namespace mbgl