Enforce Offline tile limit when merging sideloaded databases

This commit is contained in:
Asheem Mamoowala
2018-08-21 17:41:22 -07:00
parent eb72fe580b
commit d4f4136414
3 changed files with 68 additions and 9 deletions

View File

@@ -106,12 +106,18 @@ public:
* executed on the database thread; it is the responsibility of the SDK bindings
* to re-execute a user-provided callback on the main thread.
*
* The secondary database may need to be upgraded to the latest schema. This is done
* in-place and requires write-access to `sideDatabasePath`; it is the
* responsibility of the SDK bindings to ensure that this path is writeable.
*
* Only resources and tiles that belong to a region will be copied over. Identical
* regions will be flattened into a single new region in the main database.
*
* Note that the resulting new regions may not be in a completed status if the
* secondary database does not contain all the tiles or resources required by the
* region definition.
* Invokes the callback with a `MapboxOfflineTileCountExceededException` error if
* the merge operation would result in the offline tile count limit being exceeded.
*
* Merged regions may not be in a completed status if the secondary database
* does not contain all the tiles or resources required by the region definition.
*/
void mergeOfflineRegions(const std::string& sideDatabasePath,
std::function<void (expected<OfflineRegions, std::exception_ptr>)>);

View File

@@ -661,23 +661,45 @@ OfflineDatabase::mergeDatabase(const std::string& sideDatabasePath) {
throw std::runtime_error("Merge database has incorrect user_version");
}
auto currentTileCount = getOfflineMapboxTileCount();
// clang-format off
mapbox::sqlite::Query queryTiles{ getStatement(
"SELECT COUNT(DISTINCT st.id) "
"FROM side.tiles st "
//only consider region tiles, and not ambient tiles.
"JOIN side.region_tiles srt ON srt.tile_id = st.id "
"LEFT JOIN tiles t ON st.url_template = t.url_template AND "
"st.pixel_ratio = t.pixel_ratio AND "
"st.z = t.z AND "
"st.x = t.x AND "
"st.y = t.y "
"WHERE t.id IS NULL "
"AND st.url_template LIKE 'mapbox://%' ") };
// clang-format on
queryTiles.run();
auto countOfTilesToMerge = queryTiles.get<int64_t>(0);
if ((countOfTilesToMerge + currentTileCount) > offlineMapboxTileCountLimit) {
throw MapboxTileLimitExceededException();
}
queryTiles.reset();
mapbox::sqlite::Transaction transaction(*db);
db->exec(mergeSideloadedDatabaseSQL);
transaction.commit();
// clang-format off
mapbox::sqlite::Query query{ getStatement(
mapbox::sqlite::Query queryRegions{ getStatement(
"SELECT r.id, r.definition, r.description "
"FROM side.regions sr "
"JOIN regions r ON sr.definition = r.definition") };
// clang-format on
OfflineRegions result;
while (query.run()) {
while (queryRegions.run()) {
// Construct, then move because this constructor is private.
OfflineRegion region(query.get<int64_t>(0),
decodeOfflineRegionDefinition(query.get<std::string>(1)),
query.get<std::vector<uint8_t>>(2));
OfflineRegion region(queryRegions.get<int64_t>(0),
decodeOfflineRegionDefinition(queryRegions.get<std::string>(1)),
queryRegions.get<std::vector<uint8_t>>(2));
result.emplace_back(std::move(region));
}
db->exec("DETACH DATABASE side");
@@ -797,7 +819,7 @@ void OfflineDatabase::putRegionResources(int64_t regionID,
completedTileSize += resourceSize;
}
} catch (const MapboxTileLimitExceededException&) {
// Commit the rest of the batch and retrow
// Commit the rest of the batch and rethrow
transaction.commit();
throw;
}

View File

@@ -1266,6 +1266,37 @@ TEST(OfflineDatabase, MergeDatabaseWithMultipleRegionsWithOverlap) {
}
}
TEST(OfflineDatabase, MergeDatabaseWithSingleRegionTooManyNewTiles) {
FixtureLog log;
util::deleteFile(filename_sideload);
util::copyFile(filename_sideload, "test/fixtures/offline_database/sideload_sat_multiple.db");
OfflineDatabase db(":memory:");
db.setOfflineMapboxTileCountLimit(1);
auto result = db.mergeDatabase(filename_sideload);
EXPECT_FALSE(result);
EXPECT_EQ(1u, log.count({ EventSeverity::Error, Event::Database, -1, "Mapbox tile limit exceeded" }));
EXPECT_EQ(0u, log.uncheckedCount());
}
TEST(OfflineDatabase, MergeDatabaseWithSingleRegionTooManyExistingTiles) {
FixtureLog log;
deleteDatabaseFiles();
util::deleteFile(filename_sideload);
util::copyFile(filename, "test/fixtures/offline_database/sideload_sat_multiple.db");
util::copyFile(filename_sideload, "test/fixtures/offline_database/satellite_test.db");
OfflineDatabase db(filename);
db.setOfflineMapboxTileCountLimit(2);
auto result = db.mergeDatabase(filename_sideload);
EXPECT_THROW(std::rethrow_exception(result.error()), MapboxTileLimitExceededException);
EXPECT_EQ(1u, log.count({ EventSeverity::Error, Event::Database, -1, "Mapbox tile limit exceeded" }));
EXPECT_EQ(0u, log.uncheckedCount());
}
TEST(OfflineDatabase, MergeDatabaseWithInvalidPath) {
FixtureLog log;