diff --git a/include/mbgl/storage/default_file_source.hpp b/include/mbgl/storage/default_file_source.hpp
index 4f4c7136c6a..4937524e372 100644
--- a/include/mbgl/storage/default_file_source.hpp
+++ b/include/mbgl/storage/default_file_source.hpp
@@ -125,11 +125,14 @@ class DefaultFileSource : public FileSource {
* region deletion is initiated, it is not legal to perform further actions with the
* region.
*
+ * Note that this operation can be potentially slow if packing the database occurs
+ * automatically (see runPackDatabaseAutomatically() and packDatabase()).
+ *
* When the operation is complete or encounters an error, the given callback 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.
*/
- void deleteOfflineRegion(OfflineRegion&&, std::function);
+ void deleteOfflineRegion(OfflineRegion&&, std::function);
/*
* Invalidate all the tiles from an offline region forcing Mapbox GL to revalidate
@@ -137,7 +140,7 @@ class DefaultFileSource : public FileSource {
* offline region and downloading it again because if the data on the cache matches
* the server, no new data gets transmitted.
*/
- void invalidateOfflineRegion(OfflineRegion&, std::function);
+ void invalidateOfflineRegion(OfflineRegion&, std::function);
/*
* Changing or bypassing this limit without permission from Mapbox is prohibited
@@ -179,7 +182,29 @@ class DefaultFileSource : public FileSource {
* executed on the database thread; it is the responsibility of the SDK bindings
* to re-execute a user-provided callback on the main thread.
*/
- void resetDatabase(std::function);
+ void resetDatabase(std::function);
+
+ /*
+ * Packs the existing database file into a minimal amount of disk space.
+ *
+ * This operation has a performance impact as it will vacuum the database,
+ * forcing it to move pages on the filesystem.
+ *
+ * When the operation is complete or encounters an error, the given callback 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.
+ */
+ void packDatabase(std::function callback);
+
+ /*
+ * Sets whether packing the database file occurs automatically after an offline
+ * region is deleted (deleteOfflineRegion()) or the ambient cache is cleared
+ * (clearAmbientCache()).
+ *
+ * By default, packing is enabled. If disabled, disk space will not be freed
+ * after resources are removed unless packDatabase() is explicitly called.
+ */
+ void runPackDatabaseAutomatically(bool);
/*
* Forces revalidation of the ambient cache.
@@ -198,9 +223,10 @@ class DefaultFileSource : public FileSource {
/*
* Erase resources from the ambient cache, freeing storage space.
*
- * Erases the ambient cache, freeing resources. This operation can be
- * potentially slow because it will trigger a VACUUM on SQLite,
- * forcing the database to move pages on the filesystem.
+ * Erases the ambient cache, freeing resources.
+ *
+ * Note that this operation can be potentially slow if packing the database
+ * occurs automatically (see runPackDatabaseAutomatically() and packDatabase()).
*
* Resources overlapping with offline regions will not be affected
* by this call.
diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md
index 2631a4f4234..cdca5ecfc5f 100644
--- a/platform/android/CHANGELOG.md
+++ b/platform/android/CHANGELOG.md
@@ -11,6 +11,8 @@ Mapbox welcomes participation and contributions from everyone. If you'd like to
### Performance improvements
- Enable incremental vacuum for the offline database in order to make data removal requests faster and to avoid the excessive disk space usage (creating a backup file on VACUUM call) [#15837](https://github.com/mapbox/mapbox-gl-native/pull/15837)
+ - Introduce `OfflineManager.packDatabase()` API, which explicitly packs the database file (i.e. runs incremental vacuum for the offline database) [#15899](https://github.com/mapbox/mapbox-gl-native/pull/15899)
+ - Introduce `OfflineManager.runPackDatabaseAutomatically(boolean)`, which sets whether database file packing (i.e. incremental vacuum operation for the offline database) occurs automatically. [#15967](https://github.com/mapbox/mapbox-gl-native/pull/15967)
## 8.5.0-alpha.2 - October 10, 2019
[Changes](https://github.com/mapbox/mapbox-gl-native/compare/android-v8.5.0-alpha.1...android-v8.5.0-alpha.2) since [Mapbox Maps SDK for Android v8.5.0-alpha.1](https://github.com/mapbox/mapbox-gl-native/releases/tag/android-v8.5.0-alpha.1):
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java
index faed46662ab..a7bb9a99850 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java
@@ -306,6 +306,50 @@ public void run() {
});
}
+ /**
+ * Packs the existing database file into a minimal amount of disk space.
+ *
+ * This operation has a performance impact as it will vacuum the database,
+ * forcing it to move pages on the filesystem.
+ *
+ * When the operation is complete or encounters an error, the given callback 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.
+ *
+ *
+ * @param callback the callback to be invoked when the database was reset or when the operation erred.
+ */
+ public void packDatabase(@Nullable final FileSourceCallback callback) {
+ fileSource.activate();
+ nativePackDatabase(new FileSourceCallback() {
+ @Override
+ public void onSuccess() {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ fileSource.deactivate();
+ if (callback != null) {
+ callback.onSuccess();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onError(@NonNull final String message) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ fileSource.deactivate();
+ if (callback != null) {
+ callback.onError(message);
+ }
+ }
+ });
+ }
+ });
+ }
+
/**
* Forces re-validation of the ambient cache.
*
@@ -355,9 +399,10 @@ public void run() {
/**
* Erase resources from the ambient cache, freeing storage space.
*
- * Erases the ambient cache, freeing resources. This operation can be
- * potentially slow because it will trigger a VACUUM on SQLite,
- * forcing the database to move pages on the filesystem.
+ * Erases the ambient cache, freeing resources.
+ *
+ * Note that this operation can be potentially slow if packing the database
+ * occurs automatically ({@link OfflineManager#runPackDatabaseAutomatically(boolean)}).
*
*
* Resources overlapping with offline regions will not be affected
@@ -620,7 +665,7 @@ private boolean isValidOfflineRegionDefinition(OfflineRegionDefinition definitio
*
* Once this limit is reached, {@link OfflineRegion.OfflineRegionObserver#mapboxTileCountLimitExceeded(long)}
* fires every additional attempt to download additional tiles until already downloaded tiles are removed
- * by calling {@link OfflineRegion#deleteOfflineRegion(OfflineRegion.OfflineRegionDeleteCallback)}.
+ * by calling {@link OfflineRegion#delete(OfflineRegion.OfflineRegionDeleteCallback)}.
*
*
* @param limit the maximum number of tiles allowed to be downloaded
@@ -628,6 +673,24 @@ private boolean isValidOfflineRegionDefinition(OfflineRegionDefinition definitio
@Keep
public native void setOfflineMapboxTileCountLimit(long limit);
+ /**
+ * Sets whether database file packing occurs automatically.
+ * By default, the automatic database file packing is enabled.
+ *
+ * If packing is enabled, database file packing occurs automatically
+ * after an offline region is deleted by calling
+ * {@link OfflineRegion#delete(OfflineRegion.OfflineRegionDeleteCallback)}
+ * or the ambient cache is cleared by calling {@link OfflineManager#clearAmbientCache()}.
+ *
+ * If packing is disabled, disk space will not be freed after
+ * resources are removed unless {@link OfflineManager#packDatabase()} is explicitly called.
+ *
+ *
+ * @param autopack flag setting the automatic database file packing.
+ */
+ @Keep
+ public native void runPackDatabaseAutomatically(boolean autopack);
+
@Keep
private native void initialize(FileSource fileSource);
@@ -648,6 +711,9 @@ private native void createOfflineRegion(FileSource fileSource, OfflineRegionDefi
@Keep
private native void nativeResetDatabase(@Nullable FileSourceCallback callback);
+ @Keep
+ private native void nativePackDatabase(@Nullable FileSourceCallback callback);
+
@Keep
private native void nativeInvalidateAmbientCache(@Nullable FileSourceCallback callback);
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java
index f13128da632..d116535c5a6 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java
@@ -388,6 +388,10 @@ public void run() {
* by other regions, until the database shrinks below a certain size.
*
*
+ * Note that this operation can be potentially slow if packing the database
+ * occurs automatically ({@link OfflineManager#runPackDatabaseAutomatically(boolean)}).
+ *
+ *
* When the operation is complete or encounters an error, the given callback will be
* executed on the main thread.
*
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/offline/CacheTest.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/offline/CacheTest.kt
index a3214c9f114..da4cfbaf018 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/offline/CacheTest.kt
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/offline/CacheTest.kt
@@ -85,4 +85,20 @@ class CacheTest {
}
countDownLatch.await()
}
+
+ @Test
+ fun testSetPackDatabase() {
+ rule.activity.runOnUiThread {
+ OfflineManager.getInstance(context).packDatabase(object : OfflineManager.FileSourceCallback {
+ override fun onSuccess() {
+ countDownLatch.countDown()
+ }
+
+ override fun onError(message: String) {
+ Assert.assertNull("onError should not be called", message)
+ }
+ })
+ }
+ countDownLatch.await()
+ }
}
\ No newline at end of file
diff --git a/platform/android/src/offline/offline_manager.cpp b/platform/android/src/offline/offline_manager.cpp
index 029252f7867..be864e18aa7 100644
--- a/platform/android/src/offline/offline_manager.cpp
+++ b/platform/android/src/offline/offline_manager.cpp
@@ -7,8 +7,21 @@
namespace mbgl {
namespace android {
-// OfflineManager //
+namespace {
+// Reattach, the callback comes from a different thread
+void handleException(std::exception_ptr exception,
+ const jni::Object& callback,
+ android::UniqueEnv env = android::AttachEnv()) {
+ if (exception) {
+ OfflineManager::FileSourceCallback::onError(
+ *env, callback, jni::Make(*env, mbgl::util::toString(exception)));
+ } else {
+ OfflineManager::FileSourceCallback::onSuccess(*env, callback);
+ }
+}
+} // namespace
+// OfflineManager //
OfflineManager::OfflineManager(jni::JNIEnv& env, const jni::Object& jFileSource)
: fileSource(std::static_pointer_cast(mbgl::FileSource::getSharedFileSource(FileSource::getSharedResourceOptions(env, jFileSource)))) {}
@@ -107,77 +120,56 @@ void OfflineManager::mergeOfflineRegions(jni::JNIEnv& env_, const jni::Object& callback_) {
auto globalCallback = jni::NewGlobal(env_, callback_);
- fileSource->resetDatabase([
- //Keep a shared ptr to a global reference of the callback so they are not GC'd in the meanwhile
- callback = std::make_shared(std::move(globalCallback))
- ](std::exception_ptr exception) mutable {
+ fileSource->resetDatabase(
+ [
+ // Keep a shared ptr to a global reference of the callback so they are not GC'd in the meanwhile
+ callback = std::make_shared(std::move(globalCallback))](
+ std::exception_ptr exception) mutable { handleException(exception, *callback); });
+}
- // Reattach, the callback comes from a different thread
- android::UniqueEnv env = android::AttachEnv();
+void OfflineManager::packDatabase(jni::JNIEnv& env_, const jni::Object& callback_) {
+ auto globalCallback = jni::NewGlobal(env_, callback_);
- if (exception) {
- OfflineManager::FileSourceCallback::onError(*env, *callback, jni::Make(*env, mbgl::util::toString(exception)));
- } else {
- OfflineManager::FileSourceCallback::onSuccess(*env, *callback);
- }
- });
+ fileSource->packDatabase(
+ [
+ // Keep a shared ptr to a global reference of the callback so they are not GC'd in the meanwhile
+ callback = std::make_shared(std::move(globalCallback))](
+ std::exception_ptr exception) mutable { handleException(exception, *callback); });
}
void OfflineManager::invalidateAmbientCache(jni::JNIEnv& env_, const jni::Object& callback_) {
auto globalCallback = jni::NewGlobal(env_, callback_);
- fileSource->invalidateAmbientCache([
- //Keep a shared ptr to a global reference of the callback so they are not GC'd in the meanwhile
- callback = std::make_shared(std::move(globalCallback))
- ](std::exception_ptr exception) mutable {
-
- // Reattach, the callback comes from a different thread
- android::UniqueEnv env = android::AttachEnv();
-
- if (exception) {
- OfflineManager::FileSourceCallback::onError(*env, *callback, jni::Make(*env, mbgl::util::toString(exception)));
- } else {
- OfflineManager::FileSourceCallback::onSuccess(*env, *callback);
- }
- });
+ fileSource->invalidateAmbientCache(
+ [
+ // Keep a shared ptr to a global reference of the callback so they are not GC'd in the meanwhile
+ callback = std::make_shared(std::move(globalCallback))](
+ std::exception_ptr exception) mutable { handleException(exception, *callback); });
}
void OfflineManager::clearAmbientCache(jni::JNIEnv& env_, const jni::Object& callback_) {
auto globalCallback = jni::NewGlobal(env_, callback_);
- fileSource->clearAmbientCache([
- //Keep a shared ptr to a global reference of the callback so they are not GC'd in the meanwhile
- callback = std::make_shared(std::move(globalCallback))
- ](std::exception_ptr exception) mutable {
-
- // Reattach, the callback comes from a different thread
- android::UniqueEnv env = android::AttachEnv();
-
- if (exception) {
- OfflineManager::FileSourceCallback::onError(*env, *callback, jni::Make(*env, mbgl::util::toString(exception)));
- } else {
- OfflineManager::FileSourceCallback::onSuccess(*env, *callback);
- }
- });
+ fileSource->clearAmbientCache(
+ [
+ // Keep a shared ptr to a global reference of the callback so they are not GC'd in the meanwhile
+ callback = std::make_shared(std::move(globalCallback))](
+ std::exception_ptr exception) mutable { handleException(exception, *callback); });
}
void OfflineManager::setMaximumAmbientCacheSize(jni::JNIEnv& env_, const jni::jlong size_, const jni::Object& callback_) {
auto globalCallback = jni::NewGlobal(env_, callback_);
- fileSource->setMaximumAmbientCacheSize(size_, [
- //Keep a shared ptr to a global reference of the callback so they are not GC'd in the meanwhile
- callback = std::make_shared(std::move(globalCallback))
- ](std::exception_ptr exception) mutable {
-
- // Reattach, the callback comes from a different thread
- android::UniqueEnv env = android::AttachEnv();
+ fileSource->setMaximumAmbientCacheSize(
+ size_,
+ [
+ // Keep a shared ptr to a global reference of the callback so they are not GC'd in the meanwhile
+ callback = std::make_shared(std::move(globalCallback))](
+ std::exception_ptr exception) mutable { handleException(exception, *callback); });
+}
- if (exception) {
- OfflineManager::FileSourceCallback::onError(*env, *callback, jni::Make(*env, mbgl::util::toString(exception)));
- } else {
- OfflineManager::FileSourceCallback::onSuccess(*env, *callback);
- }
- });
+void OfflineManager::runPackDatabaseAutomatically(jni::JNIEnv&, jboolean autopack) {
+ fileSource->runPackDatabaseAutomatically(autopack);
}
// FileSource::FileSourceCallback //
@@ -207,7 +199,10 @@ void OfflineManager::registerNative(jni::JNIEnv& env) {
#define METHOD(MethodPtr, name) jni::MakeNativePeerMethod(name)
- jni::RegisterNativePeer( env, javaClass, "nativePtr",
+ jni::RegisterNativePeer(
+ env,
+ javaClass,
+ "nativePtr",
jni::MakePeer&>,
"initialize",
"finalize",
@@ -216,9 +211,11 @@ void OfflineManager::registerNative(jni::JNIEnv& env) {
METHOD(&OfflineManager::createOfflineRegion, "createOfflineRegion"),
METHOD(&OfflineManager::mergeOfflineRegions, "mergeOfflineRegions"),
METHOD(&OfflineManager::resetDatabase, "nativeResetDatabase"),
+ METHOD(&OfflineManager::packDatabase, "nativePackDatabase"),
METHOD(&OfflineManager::invalidateAmbientCache, "nativeInvalidateAmbientCache"),
METHOD(&OfflineManager::clearAmbientCache, "nativeClearAmbientCache"),
METHOD(&OfflineManager::setMaximumAmbientCacheSize, "nativeSetMaximumAmbientCacheSize"),
+ METHOD(&OfflineManager::runPackDatabaseAutomatically, "runPackDatabaseAutomatically"),
METHOD(&OfflineManager::putResourceWithUrl, "putResourceWithUrl"));
}
diff --git a/platform/android/src/offline/offline_manager.hpp b/platform/android/src/offline/offline_manager.hpp
index 058cfb5b484..0af92f81158 100644
--- a/platform/android/src/offline/offline_manager.hpp
+++ b/platform/android/src/offline/offline_manager.hpp
@@ -95,12 +95,16 @@ class OfflineManager {
void resetDatabase(jni::JNIEnv&, const jni::Object& callback_);
+ void packDatabase(jni::JNIEnv&, const jni::Object& callback_);
+
void invalidateAmbientCache(jni::JNIEnv&, const jni::Object& callback_);
void clearAmbientCache(jni::JNIEnv&, const jni::Object& callback_);
void setMaximumAmbientCacheSize(jni::JNIEnv&, const jni::jlong size, const jni::Object& callback_);
+ void runPackDatabaseAutomatically(jni::JNIEnv&, jboolean autopack);
+
private:
std::shared_ptr fileSource;
};
diff --git a/platform/android/src/offline/offline_region.cpp b/platform/android/src/offline/offline_region.cpp
index ac9f491ab6f..4276ce8f45f 100644
--- a/platform/android/src/offline/offline_region.cpp
+++ b/platform/android/src/offline/offline_region.cpp
@@ -104,37 +104,40 @@ void OfflineRegion::getOfflineRegionStatus(jni::JNIEnv& env_, const jni::Object<
void OfflineRegion::deleteOfflineRegion(jni::JNIEnv& env_, const jni::Object& callback_) {
auto globalCallback = jni::NewGlobal(env_, callback_);
- fileSource->deleteOfflineRegion(std::move(*region), [
- //Ensure the object is not gc'd in the meanwhile
- callback = std::make_shared(std::move(globalCallback))
- ](std::exception_ptr error) mutable {
- // Reattach, the callback comes from a different thread
- android::UniqueEnv env = android::AttachEnv();
-
- if (error) {
- OfflineRegionDeleteCallback::onError(*env, *callback, error);
- } else {
- OfflineRegionDeleteCallback::onDelete(*env, *callback);
- }
- });
+ fileSource->deleteOfflineRegion(std::move(*region),
+ [
+ // Ensure the object is not gc'd in the meanwhile
+ callback = std::make_shared(
+ std::move(globalCallback))](std::exception_ptr error) mutable {
+ // Reattach, the callback comes from a different thread
+ android::UniqueEnv env = android::AttachEnv();
+
+ if (error) {
+ OfflineRegionDeleteCallback::onError(*env, *callback, error);
+ } else {
+ OfflineRegionDeleteCallback::onDelete(*env, *callback);
+ }
+ });
}
-void OfflineRegion::invalidateOfflineRegion(jni::JNIEnv& env_, const jni::Object& callback_) {
+void OfflineRegion::invalidateOfflineRegion(jni::JNIEnv& env_,
+ const jni::Object& callback_) {
auto globalCallback = jni::NewGlobal(env_, callback_);
- fileSource->invalidateOfflineRegion(*region, [
- //Ensure the object is not gc'd in the meanwhile
- callback = std::make_shared(std::move(globalCallback))
- ](std::exception_ptr error) mutable {
- // Reattach, the callback comes from a different thread
- android::UniqueEnv env = android::AttachEnv();
-
- if (error) {
- OfflineRegionInvalidateCallback::onError(*env, *callback, error);
- } else {
- OfflineRegionInvalidateCallback::onInvalidate(*env, *callback);
- }
- });
+ fileSource->invalidateOfflineRegion(*region,
+ [
+ // Ensure the object is not gc'd in the meanwhile
+ callback = std::make_shared(
+ std::move(globalCallback))](std::exception_ptr error) mutable {
+ // Reattach, the callback comes from a different thread
+ android::UniqueEnv env = android::AttachEnv();
+
+ if (error) {
+ OfflineRegionInvalidateCallback::onError(*env, *callback, error);
+ } else {
+ OfflineRegionInvalidateCallback::onInvalidate(*env, *callback);
+ }
+ });
}
void OfflineRegion::updateOfflineRegionMetadata(jni::JNIEnv& env_, const jni::Array& jMetadata, const jni::Object& callback_) {
@@ -206,7 +209,10 @@ void OfflineRegion::registerNative(jni::JNIEnv& env) {
#define METHOD(MethodPtr, name) jni::MakeNativePeerMethod(name)
- jni::RegisterNativePeer( env, javaClass, "nativePtr",
+ jni::RegisterNativePeer(
+ env,
+ javaClass,
+ "nativePtr",
jni::MakePeer&>,
"initialize",
"finalize",
@@ -215,8 +221,7 @@ void OfflineRegion::registerNative(jni::JNIEnv& env) {
METHOD(&OfflineRegion::getOfflineRegionStatus, "getOfflineRegionStatus"),
METHOD(&OfflineRegion::deleteOfflineRegion, "deleteOfflineRegion"),
METHOD(&OfflineRegion::invalidateOfflineRegion, "invalidateOfflineRegion"),
- METHOD(&OfflineRegion::updateOfflineRegionMetadata, "updateOfflineRegionMetadata")
- );
+ METHOD(&OfflineRegion::updateOfflineRegionMetadata, "updateOfflineRegionMetadata"));
}
// OfflineRegionObserver //
@@ -227,7 +232,7 @@ void OfflineRegion::OfflineRegionStatusCallback::onError(jni::JNIEnv& env,
const jni::Object& callback,
std::exception_ptr error) {
static auto& javaClass = jni::Class::Singleton(env);
- static auto method = javaClass.GetMethod(env, "onError");
+ static auto method = javaClass.GetMethod(env, "onError");
callback.Call(env, method, jni::Make(env, mbgl::util::toString(error)));
}
diff --git a/platform/default/include/mbgl/storage/offline_database.hpp b/platform/default/include/mbgl/storage/offline_database.hpp
index e599094a6d8..465b4dd20bf 100644
--- a/platform/default/include/mbgl/storage/offline_database.hpp
+++ b/platform/default/include/mbgl/storage/offline_database.hpp
@@ -93,6 +93,8 @@ class OfflineDatabase : private util::noncopyable {
uint64_t getOfflineMapboxTileCount();
bool exceedsOfflineMapboxTileCountLimit(const Resource&);
void markUsedResources(int64_t regionID, const std::list&);
+ std::exception_ptr pack();
+ void runPackDatabaseAutomatically(bool autopack_) { autopack = autopack_; }
private:
void initialize();
@@ -147,6 +149,7 @@ class OfflineDatabase : private util::noncopyable {
optional offlineMapboxTileCount;
bool evict(uint64_t neededFreeSize);
+ bool autopack = true;
};
} // namespace mbgl
diff --git a/platform/default/src/mbgl/storage/default_file_source.cpp b/platform/default/src/mbgl/storage/default_file_source.cpp
index dc4b2908a8c..82073109e4e 100644
--- a/platform/default/src/mbgl/storage/default_file_source.cpp
+++ b/platform/default/src/mbgl/storage/default_file_source.cpp
@@ -82,7 +82,7 @@ class DefaultFileSource::Impl {
}
}
- void deleteRegion(OfflineRegion&& region, std::function callback) {
+ void deleteRegion(OfflineRegion&& region, std::function callback) {
downloads.erase(region.getID());
callback(offlineDatabase->deleteRegion(std::move(region)));
}
@@ -200,6 +200,10 @@ class DefaultFileSource::Impl {
callback(offlineDatabase->setMaximumAmbientCacheSize(size));
}
+ void packDatabase(std::function callback) { callback(offlineDatabase->pack()); }
+
+ void runPackDatabaseAutomatically(bool autopack) { offlineDatabase->runPackDatabaseAutomatically(autopack); }
+
private:
expected getDownload(int64_t regionID) {
auto it = downloads.find(regionID);
@@ -308,11 +312,12 @@ void DefaultFileSource::updateOfflineMetadata(const int64_t regionID,
impl->actor().invoke(&Impl::updateMetadata, regionID, metadata, callback);
}
-void DefaultFileSource::deleteOfflineRegion(OfflineRegion&& region, std::function callback) {
+void DefaultFileSource::deleteOfflineRegion(OfflineRegion&& region, std::function callback) {
impl->actor().invoke(&Impl::deleteRegion, std::move(region), callback);
}
-void DefaultFileSource::invalidateOfflineRegion(OfflineRegion& region, std::function callback) {
+void DefaultFileSource::invalidateOfflineRegion(OfflineRegion& region,
+ std::function callback) {
impl->actor().invoke(&Impl::invalidateRegion, region.getID(), callback);
}
@@ -348,11 +353,19 @@ void DefaultFileSource::resetDatabase(std::function c
impl->actor().invoke(&Impl::resetDatabase, std::move(callback));
}
+void DefaultFileSource::packDatabase(std::function callback) {
+ impl->actor().invoke(&Impl::packDatabase, std::move(callback));
+}
+
+void DefaultFileSource::runPackDatabaseAutomatically(bool autopack) {
+ impl->actor().invoke(&Impl::runPackDatabaseAutomatically, autopack);
+}
+
void DefaultFileSource::invalidateAmbientCache(std::function callback) {
impl->actor().invoke(&Impl::invalidateAmbientCache, std::move(callback));
}
-void DefaultFileSource::clearAmbientCache(std::function callback) {
+void DefaultFileSource::clearAmbientCache(std::function callback) {
impl->actor().invoke(&Impl::clearAmbientCache, std::move(callback));
}
diff --git a/platform/default/src/mbgl/storage/offline_database.cpp b/platform/default/src/mbgl/storage/offline_database.cpp
index 5aa5738f41f..3051f095498 100644
--- a/platform/default/src/mbgl/storage/offline_database.cpp
+++ b/platform/default/src/mbgl/storage/offline_database.cpp
@@ -140,7 +140,7 @@ void OfflineDatabase::removeExisting() {
void OfflineDatabase::removeOldCacheTable() {
assert(db);
db->exec("DROP TABLE IF EXISTS http_cache");
- vacuum();
+ if (autopack) vacuum();
}
void OfflineDatabase::createSchema() {
@@ -183,6 +183,7 @@ void OfflineDatabase::migrateToVersion6() {
}
void OfflineDatabase::vacuum() {
+ assert(db);
if (getPragma("PRAGMA auto_vacuum") != 2 /*INCREMENTAL*/) {
db->exec("PRAGMA auto_vacuum = INCREMENTAL");
db->exec("VACUUM");
@@ -693,7 +694,7 @@ std::exception_ptr OfflineDatabase::clearAmbientCache() try {
resourceQuery.run();
- vacuum();
+ if (autopack) vacuum();
return nullptr;
} catch (const mapbox::sqlite::Exception& ex) {
@@ -881,10 +882,10 @@ std::exception_ptr OfflineDatabase::deleteRegion(OfflineRegion&& region) try {
evict(0);
assert(db);
- vacuum();
+ if (autopack) vacuum();
// Ensure that the cached offlineTileCount value is recalculated.
- offlineMapboxTileCount = {};
+ offlineMapboxTileCount = nullopt;
return nullptr;
} catch (const mapbox::sqlite::Exception& ex) {
handleError(ex, "delete region");
@@ -1228,7 +1229,7 @@ std::exception_ptr OfflineDatabase::setMaximumAmbientCacheSize(uint64_t size) {
if (databaseSize > maximumAmbientCacheSize) {
evict(0);
- vacuum();
+ if (autopack) vacuum();
}
return nullptr;
@@ -1298,6 +1299,15 @@ void OfflineDatabase::markUsedResources(int64_t regionID, const std::list> generateResources(const std::string& tilePrefix,
+ const std::string& stylePrefix) {
+ static const auto responseData = randomString(.5 * 1024 * 1024);
+ Response response;
+ response.data = responseData;
+ std::list> resources;
+ for (unsigned i = 0; i < 50; ++i) {
+ resources.emplace_back(Resource::tile(tilePrefix + std::to_string(i), 1, 0, 0, 0, Tileset::Scheme::XYZ),
+ response);
+ resources.emplace_back(Resource::style(stylePrefix + std::to_string(i)), response);
+ }
+ return resources;
+}
+} // namespace
+
TEST(OfflineDatabase, TEST_REQUIRES_WRITE(DeleteRegion)) {
FixtureLog log;
deleteDatabaseFiles();
@@ -677,25 +693,36 @@ TEST(OfflineDatabase, TEST_REQUIRES_WRITE(DeleteRegion)) {
OfflineTilePyramidRegionDefinition definition{ "mapbox://style", LatLngBounds::hull({1, 2}, {3, 4}), 5, 6, 2.0, true };
OfflineRegionMetadata metadata{{ 1, 2, 3 }};
- auto region = db.createRegion(definition, metadata);
+ auto region1 = db.createRegion(definition, metadata);
+ auto region2 = db.createRegion(definition, metadata);
- for (unsigned i = 0; i < 50; ++i) {
- const Resource tile = Resource::tile("mapbox://tile_" + std::to_string(i), 1, 0, 0, 0, Tileset::Scheme::XYZ);
- db.putRegionResource(region->getID(), tile, response);
+ OfflineRegionStatus status;
+ db.putRegionResources(region1->getID(), generateResources("mapbox://tile_1", "mapbox://style_1"), status);
+ db.putRegionResources(region2->getID(), generateResources("mapbox://tile_2", "mapbox://style_2"), status);
+ const size_t sizeWithTwoRegions = util::read_file(filename).size();
- const Resource style = Resource::style("mapbox://style_" + std::to_string(i));
- db.putRegionResource(region->getID(), style, response);
- }
+ db.runPackDatabaseAutomatically(false);
+ db.deleteRegion(std::move(*region1));
- db.deleteRegion(std::move(*region));
+ ASSERT_EQ(1u, db.listRegions().value().size());
+ // Region is removed but the size of the database is the same.
+ EXPECT_EQ(sizeWithTwoRegions, util::read_file(filename).size());
- auto regions = db.listRegions().value();
- ASSERT_EQ(0u, regions.size());
+ db.pack();
+ // The size of the database has shrunk after pack().
+ const size_t sizeWithOneRegion = util::read_file(filename).size();
+ EXPECT_LT(sizeWithOneRegion, sizeWithTwoRegions);
+ db.runPackDatabaseAutomatically(true);
+ db.deleteRegion(std::move(*region2));
+ // The size of the database has shrunk right away.
+ const size_t sizeWithoutRegions = util::read_file(filename).size();
+ ASSERT_EQ(0u, db.listRegions().value().size());
+ EXPECT_LT(sizeWithoutRegions, sizeWithOneRegion);
// The tiles from the offline region will migrate to the
// ambient cache and shrink the database to the maximum
// size defined by default.
- EXPECT_LE(util::read_file(filename).size(), util::DEFAULT_MAX_CACHE_SIZE);
+ EXPECT_LE(sizeWithoutRegions, util::DEFAULT_MAX_CACHE_SIZE);
// After clearing the cache, the size of the database
// should get back to the original size.
@@ -706,6 +733,36 @@ TEST(OfflineDatabase, TEST_REQUIRES_WRITE(DeleteRegion)) {
EXPECT_EQ(0u, log.uncheckedCount());
}
+TEST(OfflineDatabase, TEST_REQUIRES_WRITE(Pack)) {
+ FixtureLog log;
+ deleteDatabaseFiles();
+
+ OfflineDatabase db(filename);
+ size_t initialSize = util::read_file(filename).size();
+ db.runPackDatabaseAutomatically(false);
+
+ Response response;
+ response.data = randomString(.5 * 1024 * 1024);
+
+ for (unsigned i = 0; i < 50; ++i) {
+ const Resource tile = Resource::tile("mapbox://tile_" + std::to_string(i), 1, 0, 0, 0, Tileset::Scheme::XYZ);
+ db.put(tile, response);
+
+ const Resource style = Resource::style("mapbox://style_" + std::to_string(i));
+ db.put(style, response);
+ }
+ size_t populatedSize = util::read_file(filename).size();
+ ASSERT_GT(populatedSize, initialSize);
+
+ db.clearAmbientCache();
+ EXPECT_EQ(populatedSize, util::read_file(filename).size());
+ EXPECT_EQ(0u, log.uncheckedCount());
+
+ db.pack();
+ EXPECT_EQ(initialSize, util::read_file(filename).size());
+ EXPECT_EQ(0u, log.uncheckedCount());
+}
+
TEST(OfflineDatabase, MapboxTileLimitExceeded) {
FixtureLog log;