Add getOfflineRegion Kotlin API (#2516)
This commit is contained in:
@@ -136,6 +136,17 @@ public:
|
||||
*/
|
||||
virtual void listOfflineRegions(std::function<void(expected<OfflineRegions, std::exception_ptr>)>);
|
||||
|
||||
/**
|
||||
* Retrieve given region in the offline database.
|
||||
*
|
||||
* The query will be executed asynchronously and the results passed to the
|
||||
* given callback, which will be executed on the database thread; it is the
|
||||
* responsibility of the SDK bindings to re-execute a user-provided callback
|
||||
* on the main thread.
|
||||
*/
|
||||
virtual void getOfflineRegion(int64_t regionID,
|
||||
std::function<void(expected<std::optional<OfflineRegion>, std::exception_ptr>)>);
|
||||
|
||||
/**
|
||||
* Create an offline region in the database.
|
||||
*
|
||||
|
||||
@@ -63,6 +63,36 @@ void OfflineManager::listOfflineRegions(jni::JNIEnv& env_,
|
||||
}
|
||||
});
|
||||
}
|
||||
void OfflineManager::getOfflineRegion(jni::JNIEnv& env_,
|
||||
const jni::Object<FileSource>& jFileSource_,
|
||||
const int64_t regionID,
|
||||
const jni::Object<GetOfflineRegionCallback>& callback_) {
|
||||
auto globalCallback = jni::NewGlobal<jni::EnvAttachingDeleter>(env_, callback_);
|
||||
auto globalFilesource = jni::NewGlobal<jni::EnvAttachingDeleter>(env_, jFileSource_);
|
||||
|
||||
fileSource->getOfflineRegion(
|
||||
regionID,
|
||||
[
|
||||
// Keep a shared ptr to a global reference of the callback and file
|
||||
// source so they are not GC'd in the meanwhile
|
||||
callback = std::make_shared<decltype(globalCallback)>(std::move(globalCallback)),
|
||||
jFileSource = std::make_shared<decltype(globalFilesource)>(std::move(globalFilesource)),
|
||||
regionID](mbgl::expected<std::optional<mbgl::OfflineRegion>, std::exception_ptr> result) mutable {
|
||||
// Reattach, the callback comes from a different thread
|
||||
android::UniqueEnv env = android::AttachEnv();
|
||||
|
||||
if (result.has_exception<std::logic_error>() || result.has_exception<std::runtime_error>()) {
|
||||
OfflineManager::GetOfflineRegionCallback::onError(*env, *callback, result.error());
|
||||
} else {
|
||||
auto region = result.value();
|
||||
if (region) {
|
||||
OfflineManager::GetOfflineRegionCallback::onRegion(*env, *jFileSource, *callback, *region);
|
||||
} else {
|
||||
OfflineManager::GetOfflineRegionCallback::onRegionNotFound(*env, *jFileSource, *callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void OfflineManager::createOfflineRegion(jni::JNIEnv& env_,
|
||||
const jni::Object<FileSource>& jFileSource_,
|
||||
@@ -206,6 +236,7 @@ void OfflineManager::FileSourceCallback::onError(jni::JNIEnv& env,
|
||||
|
||||
void OfflineManager::registerNative(jni::JNIEnv& env) {
|
||||
jni::Class<ListOfflineRegionsCallback>::Singleton(env);
|
||||
jni::Class<GetOfflineRegionCallback>::Singleton(env);
|
||||
jni::Class<CreateOfflineRegionCallback>::Singleton(env);
|
||||
jni::Class<MergeOfflineRegionsCallback>::Singleton(env);
|
||||
jni::Class<FileSourceCallback>::Singleton(env);
|
||||
@@ -223,6 +254,7 @@ void OfflineManager::registerNative(jni::JNIEnv& env) {
|
||||
"finalize",
|
||||
METHOD(&OfflineManager::setOfflineMapboxTileCountLimit, "setOfflineMapboxTileCountLimit"),
|
||||
METHOD(&OfflineManager::listOfflineRegions, "listOfflineRegions"),
|
||||
METHOD(&OfflineManager::getOfflineRegion, "getOfflineRegion"),
|
||||
METHOD(&OfflineManager::createOfflineRegion, "createOfflineRegion"),
|
||||
METHOD(&OfflineManager::mergeOfflineRegions, "mergeOfflineRegions"),
|
||||
METHOD(&OfflineManager::resetDatabase, "nativeResetDatabase"),
|
||||
@@ -264,6 +296,37 @@ void OfflineManager::ListOfflineRegionsCallback::onList(
|
||||
callback.Call(env, method, jregions);
|
||||
}
|
||||
|
||||
// OfflineManager::GetOfflineRegionCallback //
|
||||
|
||||
void OfflineManager::GetOfflineRegionCallback::onError(
|
||||
jni::JNIEnv& env, const jni::Object<OfflineManager::GetOfflineRegionCallback>& callback, std::exception_ptr error) {
|
||||
static auto& javaClass = jni::Class<OfflineManager::GetOfflineRegionCallback>::Singleton(env);
|
||||
static auto method = javaClass.GetMethod<void(jni::String)>(env, "onError");
|
||||
|
||||
callback.Call(env, method, jni::Make<jni::String>(env, mbgl::util::toString(error)));
|
||||
}
|
||||
|
||||
void OfflineManager::GetOfflineRegionCallback::onRegionNotFound(
|
||||
jni::JNIEnv& env,
|
||||
const jni::Object<FileSource>& jFileSource,
|
||||
const jni::Object<OfflineManager::GetOfflineRegionCallback>& callback) {
|
||||
static auto& javaClass = jni::Class<OfflineManager::GetOfflineRegionCallback>::Singleton(env);
|
||||
static auto method = javaClass.GetMethod<void(void)>(env, "onRegionNotFound");
|
||||
|
||||
callback.Call(env, method);
|
||||
}
|
||||
|
||||
void OfflineManager::GetOfflineRegionCallback::onRegion(
|
||||
jni::JNIEnv& env,
|
||||
const jni::Object<FileSource>& jFileSource,
|
||||
const jni::Object<OfflineManager::GetOfflineRegionCallback>& callback,
|
||||
mbgl::OfflineRegion& region) {
|
||||
static auto& javaClass = jni::Class<OfflineManager::GetOfflineRegionCallback>::Singleton(env);
|
||||
static auto method = javaClass.GetMethod<void(jni::Object<OfflineRegion>)>(env, "onRegion");
|
||||
|
||||
callback.Call(env, method, OfflineRegion::New(env, jFileSource, std::move(region)));
|
||||
}
|
||||
|
||||
// OfflineManager::CreateOfflineRegionCallback //
|
||||
|
||||
void OfflineManager::CreateOfflineRegionCallback::onError(
|
||||
|
||||
@@ -32,6 +32,26 @@ public:
|
||||
const jni::Object<OfflineManager::ListOfflineRegionsCallback>&,
|
||||
mbgl::OfflineRegions&);
|
||||
};
|
||||
class GetOfflineRegionCallback {
|
||||
public:
|
||||
static constexpr auto Name() {
|
||||
return "org/maplibre/android/offline/"
|
||||
"OfflineManager$GetOfflineRegionCallback";
|
||||
}
|
||||
|
||||
static void onError(jni::JNIEnv&,
|
||||
const jni::Object<OfflineManager::GetOfflineRegionCallback>&,
|
||||
std::exception_ptr);
|
||||
|
||||
static void onRegionNotFound(jni::JNIEnv&,
|
||||
const jni::Object<FileSource>&,
|
||||
const jni::Object<OfflineManager::GetOfflineRegionCallback>&);
|
||||
|
||||
static void onRegion(jni::JNIEnv&,
|
||||
const jni::Object<FileSource>&,
|
||||
const jni::Object<OfflineManager::GetOfflineRegionCallback>&,
|
||||
mbgl::OfflineRegion&);
|
||||
};
|
||||
|
||||
class CreateOfflineRegionCallback {
|
||||
public:
|
||||
@@ -91,6 +111,11 @@ public:
|
||||
const jni::Object<FileSource>&,
|
||||
const jni::Object<ListOfflineRegionsCallback>& callback);
|
||||
|
||||
void getOfflineRegion(jni::JNIEnv&,
|
||||
const jni::Object<FileSource>&,
|
||||
const int64_t regionID,
|
||||
const jni::Object<GetOfflineRegionCallback>& callback);
|
||||
|
||||
void createOfflineRegion(jni::JNIEnv&,
|
||||
const jni::Object<FileSource>& jFileSource_,
|
||||
const jni::Object<OfflineRegionDefinition>& definition,
|
||||
|
||||
@@ -60,6 +60,32 @@ class OfflineManager private constructor(context: Context) {
|
||||
fun onError(error: String)
|
||||
}
|
||||
|
||||
/**
|
||||
* This callback receives an asynchronous response containing the
|
||||
* OfflineRegion in the database or an error message otherwise.
|
||||
*/
|
||||
@Keep
|
||||
interface GetOfflineRegionCallback {
|
||||
/**
|
||||
* Receives the offline region.
|
||||
*
|
||||
* @param offlineRegion the offline region if it exists
|
||||
*/
|
||||
fun onRegion(offlineRegion: OfflineRegion)
|
||||
|
||||
/**
|
||||
* Receives notice if the requested regionId is not in the database
|
||||
*/
|
||||
fun onRegionNotFound()
|
||||
|
||||
/**
|
||||
* Receives the error message.
|
||||
*
|
||||
* @param error the error message
|
||||
*/
|
||||
fun onError(error: String)
|
||||
}
|
||||
|
||||
/**
|
||||
* This callback receives an asynchronous response containing the newly created
|
||||
* OfflineRegion in the database or an error message otherwise.
|
||||
@@ -146,6 +172,44 @@ class OfflineManager private constructor(context: Context) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve given region in the offline database.
|
||||
*
|
||||
* The query will be executed asynchronously and the results passed to the given
|
||||
* callback on the main thread.
|
||||
*
|
||||
* @param callback the callback to be invoked
|
||||
*/
|
||||
fun getOfflineRegion(regionID: Long, callback: GetOfflineRegionCallback) {
|
||||
fileSource.activate()
|
||||
getOfflineRegion(
|
||||
fileSource,
|
||||
regionID,
|
||||
object : GetOfflineRegionCallback {
|
||||
override fun onRegion(offlineRegion: OfflineRegion) {
|
||||
handler.post {
|
||||
fileSource.deactivate()
|
||||
callback.onRegion(offlineRegion)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRegionNotFound() {
|
||||
handler.post {
|
||||
fileSource.deactivate()
|
||||
callback.onRegionNotFound()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(error: String) {
|
||||
handler.post {
|
||||
fileSource.deactivate()
|
||||
callback.onError(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge offline regions from a secondary database into the main offline database.
|
||||
*
|
||||
@@ -516,6 +580,9 @@ class OfflineManager private constructor(context: Context) {
|
||||
@Keep
|
||||
private external fun listOfflineRegions(fileSource: FileSource, callback: ListOfflineRegionsCallback)
|
||||
|
||||
@Keep
|
||||
private external fun getOfflineRegion(fileSource: FileSource, regionID: Long, callback: GetOfflineRegionCallback)
|
||||
|
||||
@Keep
|
||||
private external fun createOfflineRegion(fileSource: FileSource, definition: OfflineRegionDefinition, metadata: ByteArray, callback: CreateOfflineRegionCallback)
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ class OfflineManagerTest : AppCenter() {
|
||||
|
||||
// List regions (ensure merge yielded one region only)
|
||||
|
||||
val latch3 = CountDownLatch(1)
|
||||
val latch3 = CountDownLatch(2)
|
||||
rule.activity.runOnUiThread {
|
||||
OfflineManager.getInstance(context).listOfflineRegions(object : OfflineManager.ListOfflineRegionsCallback {
|
||||
override fun onList(offlineRegions: Array<OfflineRegion>?) {
|
||||
@@ -114,6 +114,29 @@ class OfflineManagerTest : AppCenter() {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
val regionID = 1L
|
||||
rule.activity.runOnUiThread {
|
||||
OfflineManager.getInstance(context).getOfflineRegion(
|
||||
regionID,
|
||||
object: OfflineManager.GetOfflineRegionCallback {
|
||||
override fun onRegion(offlineRegion: OfflineRegion) {
|
||||
Assert.assertNotNull(offlineRegion)
|
||||
Assert.assertEquals(regionID, offlineRegion!!.id)
|
||||
Assert.assertNotNull(offlineRegion.definition)
|
||||
latch3.countDown()
|
||||
}
|
||||
|
||||
override fun onRegionNotFound() {
|
||||
throw RuntimeException("Region should be in database")
|
||||
}
|
||||
|
||||
override fun onError(error: String) {
|
||||
throw RuntimeException("Unable to get region in offline database. $error")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
latch3.await()
|
||||
|
||||
// Invalidate region
|
||||
@@ -150,7 +173,7 @@ class OfflineManagerTest : AppCenter() {
|
||||
|
||||
// List regions (ensure deletion was successful)
|
||||
|
||||
val latch6 = CountDownLatch(1)
|
||||
val latch6 = CountDownLatch(2)
|
||||
rule.activity.runOnUiThread {
|
||||
OfflineManager.getInstance(context).listOfflineRegions(object : OfflineManager.ListOfflineRegionsCallback {
|
||||
override fun onList(offlineRegions: Array<OfflineRegion>?) {
|
||||
@@ -163,6 +186,25 @@ class OfflineManagerTest : AppCenter() {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
rule.activity.runOnUiThread {
|
||||
OfflineManager.getInstance(context).getOfflineRegion(
|
||||
regionID,
|
||||
object: OfflineManager.GetOfflineRegionCallback {
|
||||
override fun onRegion(offlineRegion: OfflineRegion) {
|
||||
throw RuntimeException("Region should not be in database")
|
||||
}
|
||||
|
||||
override fun onRegionNotFound() {
|
||||
latch6.countDown()
|
||||
}
|
||||
|
||||
override fun onError(error: String) {
|
||||
throw RuntimeException("Unable to get offline region from db. $error")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
latch6.await()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,8 @@ public:
|
||||
|
||||
expected<OfflineRegions, std::exception_ptr> listRegions();
|
||||
|
||||
expected<std::optional<OfflineRegion>, std::exception_ptr> getRegion(const int64_t regionID);
|
||||
|
||||
expected<OfflineRegion, std::exception_ptr> createRegion(const OfflineRegionDefinition&,
|
||||
const OfflineRegionMetadata&);
|
||||
|
||||
|
||||
@@ -76,6 +76,11 @@ public:
|
||||
callback(db->listRegions());
|
||||
}
|
||||
|
||||
void getRegion(const int64_t regionID,
|
||||
const std::function<void(expected<std::optional<OfflineRegion>, std::exception_ptr>)>& callback) {
|
||||
callback(db->getRegion(regionID));
|
||||
}
|
||||
|
||||
void createRegion(const OfflineRegionDefinition& definition,
|
||||
const OfflineRegionMetadata& metadata,
|
||||
const std::function<void(expected<OfflineRegion, std::exception_ptr>)>& callback) {
|
||||
@@ -265,6 +270,11 @@ void DatabaseFileSource::listOfflineRegions(
|
||||
impl->actor().invoke(&DatabaseFileSourceThread::listRegions, std::move(callback));
|
||||
}
|
||||
|
||||
void DatabaseFileSource::getOfflineRegion(
|
||||
const int64_t regionID, std::function<void(expected<std::optional<OfflineRegion>, std::exception_ptr>)> callback) {
|
||||
impl->actor().invoke(&DatabaseFileSourceThread::getRegion, regionID, std::move(callback));
|
||||
}
|
||||
|
||||
void DatabaseFileSource::createOfflineRegion(
|
||||
const OfflineRegionDefinition& definition,
|
||||
const OfflineRegionMetadata& metadata,
|
||||
|
||||
@@ -835,6 +835,26 @@ expected<OfflineRegions, std::exception_ptr> OfflineDatabase::listRegions() try
|
||||
return unexpected<std::exception_ptr>(std::current_exception());
|
||||
}
|
||||
|
||||
expected<std::optional<OfflineRegion>, std::exception_ptr> OfflineDatabase::getRegion(const int64_t regionID) try {
|
||||
mapbox::sqlite::Query query{getStatement("SELECT definition, description FROM regions WHERE id = ?1")};
|
||||
query.bind(1, regionID);
|
||||
if (query.run()) {
|
||||
const auto definition = query.get<std::string>(0);
|
||||
const auto description = query.get<std::vector<uint8_t>>(1);
|
||||
|
||||
if (definition.empty() && description.empty()) {
|
||||
throw std::runtime_error("Region " + std::to_string(regionID) + " not found in database");
|
||||
}
|
||||
|
||||
OfflineRegion result(regionID, decodeOfflineRegionDefinition(definition), description);
|
||||
return {std::move(result)};
|
||||
}
|
||||
return std::nullopt;
|
||||
} catch (...) {
|
||||
handleError("get region");
|
||||
return unexpected<std::exception_ptr>(std::current_exception());
|
||||
}
|
||||
|
||||
expected<OfflineRegion, std::exception_ptr> OfflineDatabase::createRegion(const OfflineRegionDefinition& definition,
|
||||
const OfflineRegionMetadata& metadata) try {
|
||||
checkFlags();
|
||||
|
||||
@@ -465,8 +465,8 @@ TEST(OfflineDatabase, UpdateMetadata) {
|
||||
|
||||
OfflineRegionMetadata newmetadata{{4, 5, 6}};
|
||||
db.updateMetadata(region->getID(), newmetadata);
|
||||
auto regions = db.listRegions().value();
|
||||
EXPECT_EQ(regions.at(0).getMetadata(), newmetadata);
|
||||
auto newRegion = db.getRegion(region->getID()).value();
|
||||
EXPECT_EQ(newRegion->getMetadata(), newmetadata);
|
||||
|
||||
EXPECT_EQ(0u, log.uncheckedCount());
|
||||
}
|
||||
@@ -502,6 +502,36 @@ TEST(OfflineDatabase, ListRegions) {
|
||||
EXPECT_EQ(0u, log.uncheckedCount());
|
||||
}
|
||||
|
||||
TEST(OfflineDatabase, GetRegion) {
|
||||
FixtureLog log;
|
||||
OfflineDatabase db(":memory:", fixture::tileServerOptions);
|
||||
OfflineTilePyramidRegionDefinition definition{
|
||||
"http://example.com/style", LatLngBounds::hull({1, 2}, {3, 4}), 5, 6, 2.0, false};
|
||||
OfflineRegionMetadata metadata{{1, 2, 3}};
|
||||
|
||||
auto createdRegion = db.createRegion(definition, metadata);
|
||||
ASSERT_TRUE(createdRegion);
|
||||
auto regionId = createdRegion->getID();
|
||||
auto region = db.getRegion(regionId).value();
|
||||
|
||||
EXPECT_EQ(regionId, region->getID());
|
||||
std::visit(overloaded{[&](OfflineTilePyramidRegionDefinition& def) {
|
||||
EXPECT_EQ(definition.styleURL, def.styleURL);
|
||||
EXPECT_EQ(definition.bounds, def.bounds);
|
||||
EXPECT_EQ(definition.minZoom, def.minZoom);
|
||||
EXPECT_EQ(definition.maxZoom, def.maxZoom);
|
||||
EXPECT_EQ(definition.pixelRatio, def.pixelRatio);
|
||||
EXPECT_EQ(definition.includeIdeographs, def.includeIdeographs);
|
||||
},
|
||||
[&](auto&) {
|
||||
EXPECT_FALSE(false);
|
||||
}},
|
||||
region->getDefinition());
|
||||
EXPECT_EQ(metadata, region->getMetadata());
|
||||
|
||||
EXPECT_EQ(0u, log.uncheckedCount());
|
||||
}
|
||||
|
||||
TEST(OfflineDatabase, GetRegionDefinition) {
|
||||
FixtureLog log;
|
||||
OfflineDatabase db(":memory:", fixture::tileServerOptions);
|
||||
|
||||
Reference in New Issue
Block a user