diff options
author | Konstantin Käfer <mail@kkaefer.com> | 2018-06-13 15:50:23 +0200 |
---|---|---|
committer | Konstantin Käfer <mail@kkaefer.com> | 2018-08-14 17:03:46 -0700 |
commit | fa7b70945fe6927536869d7dd66de0834d1e8002 (patch) | |
tree | abd25881038ef21467e9a4d56dc6918f1a690a43 /platform | |
parent | a3b6fc149895a7f4c29acd6bba70546edec41438 (diff) | |
download | qtlocation-mapboxgl-fa7b70945fe6927536869d7dd66de0834d1e8002.tar.gz |
[core] harden OfflineDatabase
Diffstat (limited to 'platform')
-rw-r--r-- | platform/default/default_file_source.cpp | 28 | ||||
-rw-r--r-- | platform/default/mbgl/storage/offline_database.cpp | 313 | ||||
-rw-r--r-- | platform/default/mbgl/storage/offline_database.hpp | 24 | ||||
-rw-r--r-- | platform/default/mbgl/storage/offline_download.cpp | 31 | ||||
-rw-r--r-- | platform/default/sqlite3.cpp | 48 | ||||
-rw-r--r-- | platform/qt/src/sqlite3.cpp | 12 |
6 files changed, 297 insertions, 159 deletions
diff --git a/platform/default/default_file_source.cpp b/platform/default/default_file_source.cpp index f070121497..051e1b125c 100644 --- a/platform/default/default_file_source.cpp +++ b/platform/default/default_file_source.cpp @@ -74,9 +74,9 @@ public: } void getRegionStatus(int64_t regionID, std::function<void (std::exception_ptr, optional<OfflineRegionStatus>)> callback) { - try { - callback({}, getDownload(regionID).getStatus()); - } catch (...) { + if (auto download = getDownload(regionID)) { + callback({}, download->getStatus()); + } else { callback(std::current_exception(), {}); } } @@ -92,11 +92,15 @@ public: } void setRegionObserver(int64_t regionID, std::unique_ptr<OfflineRegionObserver> observer) { - getDownload(regionID).setObserver(std::move(observer)); + if (auto download = getDownload(regionID)) { + download->setObserver(std::move(observer)); + } } void setRegionDownloadState(int64_t regionID, OfflineRegionDownloadState state) { - getDownload(regionID).setState(state); + if (auto download = getDownload(regionID)) { + download->setState(state); + } } void request(AsyncRequest* req, Resource resource, ActorRef<FileSourceRequest> ref) { @@ -181,13 +185,19 @@ public: } private: - OfflineDownload& getDownload(int64_t regionID) { + OfflineDownload* getDownload(int64_t regionID) { auto it = downloads.find(regionID); if (it != downloads.end()) { - return *it->second; + return it->second.get(); + } + auto definition = offlineDatabase->getRegionDefinition(regionID); + if (!definition) { + return nullptr; } - return *downloads.emplace(regionID, - std::make_unique<OfflineDownload>(regionID, offlineDatabase->getRegionDefinition(regionID), *offlineDatabase, onlineFileSource)).first->second; + + auto download = std::make_unique<OfflineDownload>(regionID, std::move(*definition), + *offlineDatabase, onlineFileSource); + return downloads.emplace(regionID, std::move(download)).first->second.get(); } // shared so that destruction is done on the creating thread diff --git a/platform/default/mbgl/storage/offline_database.cpp b/platform/default/mbgl/storage/offline_database.cpp index 8f7f0965f4..cfa7f52977 100644 --- a/platform/default/mbgl/storage/offline_database.cpp +++ b/platform/default/mbgl/storage/offline_database.cpp @@ -15,7 +15,14 @@ namespace mbgl { OfflineDatabase::OfflineDatabase(std::string path_, uint64_t maximumCacheSize_) : path(std::move(path_)), maximumCacheSize(maximumCacheSize_) { - ensureSchema(); + try { + initialize(); + } catch (const util::IOException& ex) { + handleError(ex, "open database"); + } catch (const mapbox::sqlite::Exception& ex) { + handleError(ex, "open database"); + } + // Assume that we can't open the database right now and work with an empty database object. } OfflineDatabase::~OfflineDatabase() { @@ -24,87 +31,71 @@ OfflineDatabase::~OfflineDatabase() { try { statements.clear(); db.reset(); - } catch (mapbox::sqlite::Exception& ex) { - Log::Error(Event::Database, (int)ex.code, ex.what()); + } catch (const util::IOException& ex) { + handleError(ex, "close database"); + } catch (const mapbox::sqlite::Exception& ex) { + handleError(ex, "close database"); } } -void OfflineDatabase::ensureSchema() { - auto result = mapbox::sqlite::Database::tryOpen(path, mapbox::sqlite::ReadWriteCreate); - if (result.is<mapbox::sqlite::Exception>()) { - const auto& ex = result.get<mapbox::sqlite::Exception>(); - if (ex.code == mapbox::sqlite::ResultCode::NotADB) { - // Corrupted; blow it away. - removeExisting(); - result = mapbox::sqlite::Database::open(path, mapbox::sqlite::ReadWriteCreate); - } else { - Log::Error(Event::Database, "Unexpected error connecting to database: %s", ex.what()); - throw ex; - } +void OfflineDatabase::initialize() { + assert(!db); + assert(statements.empty()); + + db = std::make_unique<mapbox::sqlite::Database>( + mapbox::sqlite::Database::open(path, mapbox::sqlite::ReadWriteCreate)); + db->setBusyTimeout(Milliseconds::max()); + db->exec("PRAGMA foreign_keys = ON"); + + const auto userVersion = getPragma<int64_t>("PRAGMA user_version"); + switch (userVersion) { + case 0: + case 1: + // Newly created database, or old cache-only database; remove old table if it exists. + removeOldCacheTable(); + createSchema(); + return; + case 2: + migrateToVersion3(); + // fall through + case 3: + // Removed migration, see below. + // fall through + case 4: + migrateToVersion5(); + // fall through + case 5: + migrateToVersion6(); + // fall through + case 6: + // Happy path; we're done + return; + default: + // Downgrade: delete the database and try to reinitialize. + removeExisting(); + initialize(); } +} - try { - assert(result.is<mapbox::sqlite::Database>()); - db = std::make_unique<mapbox::sqlite::Database>(std::move(result.get<mapbox::sqlite::Database>())); - db->setBusyTimeout(Milliseconds::max()); - db->exec("PRAGMA foreign_keys = ON"); - - switch (userVersion()) { - case 0: - case 1: - // Newly created database, or old cache-only database; remove old table if it exists. - removeOldCacheTable(); - break; - case 2: - migrateToVersion3(); - // fall through - case 3: - case 4: - migrateToVersion5(); - // fall through - case 5: - migrateToVersion6(); - // fall through - case 6: - // happy path; we're done - return; - default: - // downgrade, delete the database - removeExisting(); - break; - } - } catch (const mapbox::sqlite::Exception& ex) { - // Unfortunately, SQLITE_NOTADB is not always reported upon opening the database. - // Apparently sometimes it is delayed until the first read operation. - if (ex.code == mapbox::sqlite::ResultCode::NotADB) { +void OfflineDatabase::handleError(const mapbox::sqlite::Exception& ex, const char* action) { + if (ex.code == mapbox::sqlite::ResultCode::NotADB || + ex.code == mapbox::sqlite::ResultCode::Corrupt) { + // Corrupted; delete database so that we have a clean slate for the next operation. + Log::Error(Event::Database, static_cast<int>(ex.code), "Can't %s: %s", action, ex.what()); + try { removeExisting(); - } else { - throw; + } catch (const util::IOException& ioEx) { + handleError(ioEx, action); } - } - - try { - // When downgrading the database, or when the database is corrupt, we've deleted the old database handle, - // so we need to reopen it. - if (!db) { - db = std::make_unique<mapbox::sqlite::Database>(mapbox::sqlite::Database::open(path, mapbox::sqlite::ReadWriteCreate)); - db->setBusyTimeout(Milliseconds::max()); - db->exec("PRAGMA foreign_keys = ON"); - } - - db->exec("PRAGMA auto_vacuum = INCREMENTAL"); - db->exec("PRAGMA journal_mode = DELETE"); - db->exec("PRAGMA synchronous = FULL"); - db->exec(offlineDatabaseSchema); - db->exec("PRAGMA user_version = 6"); - } catch (...) { - Log::Error(Event::Database, "Unexpected error creating database schema: %s", util::toString(std::current_exception()).c_str()); - throw; + } else { + // We treat the error as temporary, and pretend we have an inaccessible DB. + Log::Warning(Event::Database, static_cast<int>(ex.code), "Can't %s: %s", action, ex.what()); } } -int OfflineDatabase::userVersion() { - return static_cast<int>(getPragma<int64_t>("PRAGMA user_version")); +void OfflineDatabase::handleError(const util::IOException& ex, const char* action) { + // We failed to delete the database file. + Log::Error(Event::Database, ex.code, "Can't %s: %s", action, ex.what()); } void OfflineDatabase::removeExisting() { @@ -113,19 +104,28 @@ void OfflineDatabase::removeExisting() { statements.clear(); db.reset(); - try { - util::deleteFile(path); - } catch (util::IOException& ex) { - Log::Error(Event::Database, ex.code, ex.what()); - } + util::deleteFile(path); } void OfflineDatabase::removeOldCacheTable() { + assert(db); db->exec("DROP TABLE IF EXISTS http_cache"); db->exec("VACUUM"); } +void OfflineDatabase::createSchema() { + assert(db); + db->exec("PRAGMA auto_vacuum = INCREMENTAL"); + db->exec("PRAGMA journal_mode = DELETE"); + db->exec("PRAGMA synchronous = FULL"); + mapbox::sqlite::Transaction transaction(*db); + db->exec(offlineDatabaseSchema); + db->exec("PRAGMA user_version = 6"); + transaction.commit(); +} + void OfflineDatabase::migrateToVersion3() { + assert(db); db->exec("PRAGMA auto_vacuum = INCREMENTAL"); db->exec("VACUUM"); db->exec("PRAGMA user_version = 3"); @@ -138,12 +138,14 @@ void OfflineDatabase::migrateToVersion3() { // See: https://github.com/mapbox/mapbox-gl-native/pull/6320 void OfflineDatabase::migrateToVersion5() { + assert(db); db->exec("PRAGMA journal_mode = DELETE"); db->exec("PRAGMA synchronous = FULL"); db->exec("PRAGMA user_version = 5"); } void OfflineDatabase::migrateToVersion6() { + assert(db); mapbox::sqlite::Transaction transaction(*db); db->exec("ALTER TABLE resources ADD COLUMN must_revalidate INTEGER NOT NULL DEFAULT 0"); db->exec("ALTER TABLE tiles ADD COLUMN must_revalidate INTEGER NOT NULL DEFAULT 0"); @@ -152,6 +154,9 @@ void OfflineDatabase::migrateToVersion6() { } mapbox::sqlite::Statement& OfflineDatabase::getStatement(const char* sql) { + if (!db) { + initialize(); + } auto it = statements.find(sql); if (it == statements.end()) { it = statements.emplace(sql, std::make_unique<mapbox::sqlite::Statement>(*db, sql)).first; @@ -159,9 +164,15 @@ mapbox::sqlite::Statement& OfflineDatabase::getStatement(const char* sql) { return *it->second; } -optional<Response> OfflineDatabase::get(const Resource& resource) { +optional<Response> OfflineDatabase::get(const Resource& resource) try { auto result = getInternal(resource); - return result ? result->first : optional<Response>(); + return result ? optional<Response>{ result->first } : nullopt; +} catch (const util::IOException& ex) { + handleError(ex, "read resource"); + return nullopt; +} catch (const mapbox::sqlite::Exception& ex) { + handleError(ex, "read resource"); + return nullopt; } optional<std::pair<Response, uint64_t>> OfflineDatabase::getInternal(const Resource& resource) { @@ -182,11 +193,17 @@ optional<int64_t> OfflineDatabase::hasInternal(const Resource& resource) { } } -std::pair<bool, uint64_t> OfflineDatabase::put(const Resource& resource, const Response& response) { +std::pair<bool, uint64_t> OfflineDatabase::put(const Resource& resource, const Response& response) try { + if (!db) { + initialize(); + } mapbox::sqlite::Transaction transaction(*db, mapbox::sqlite::Transaction::Immediate); auto result = putInternal(resource, response, true); transaction.commit(); return result; +} catch (const mapbox::sqlite::Exception& ex) { + handleError(ex, "write resource"); + return { false, 0 }; } std::pair<bool, uint64_t> OfflineDatabase::putInternal(const Resource& resource, const Response& response, bool evict_) { @@ -227,11 +244,19 @@ std::pair<bool, uint64_t> OfflineDatabase::putInternal(const Resource& resource, optional<std::pair<Response, uint64_t>> OfflineDatabase::getResource(const Resource& resource) { // Update accessed timestamp used for LRU eviction. - { + try { mapbox::sqlite::Query accessedQuery{ getStatement("UPDATE resources SET accessed = ?1 WHERE url = ?2") }; accessedQuery.bind(1, util::now()); accessedQuery.bind(2, resource.url); accessedQuery.run(); + } catch (const mapbox::sqlite::Exception& ex) { + if (ex.code == mapbox::sqlite::ResultCode::NotADB || + ex.code == mapbox::sqlite::ResultCode::Corrupt) { + throw; + } + + // If we don't have any indication that the database is corrupt, continue as usual. + Log::Warning(Event::Database, static_cast<int>(ex.code), "Can't update timestamp: %s", ex.what()); } // clang-format off @@ -245,7 +270,7 @@ optional<std::pair<Response, uint64_t>> OfflineDatabase::getResource(const Resou query.bind(1, resource.url); if (!query.run()) { - return {}; + return nullopt; } Response response; @@ -274,7 +299,7 @@ optional<int64_t> OfflineDatabase::hasResource(const Resource& resource) { mapbox::sqlite::Query query{ getStatement("SELECT length(data) FROM resources WHERE url = ?") }; query.bind(1, resource.url); if (!query.run()) { - return {}; + return nullopt; } return query.get<optional<int64_t>>(0); @@ -366,7 +391,8 @@ bool OfflineDatabase::putResource(const Resource& resource, } optional<std::pair<Response, uint64_t>> OfflineDatabase::getTile(const Resource::TileData& tile) { - { + // Update accessed timestamp used for LRU eviction. + try { // clang-format off mapbox::sqlite::Query accessedQuery{ getStatement( "UPDATE tiles " @@ -385,6 +411,13 @@ optional<std::pair<Response, uint64_t>> OfflineDatabase::getTile(const Resource: accessedQuery.bind(5, tile.y); accessedQuery.bind(6, tile.z); accessedQuery.run(); + } catch (const mapbox::sqlite::Exception& ex) { + if (ex.code == mapbox::sqlite::ResultCode::NotADB || ex.code == mapbox::sqlite::ResultCode::Corrupt) { + throw; + } + + // If we don't have any indication that the database is corrupt, continue as usual. + Log::Warning(Event::Database, static_cast<int>(ex.code), "Can't update timestamp: %s", ex.what()); } // clang-format off @@ -406,7 +439,7 @@ optional<std::pair<Response, uint64_t>> OfflineDatabase::getTile(const Resource: query.bind(5, tile.z); if (!query.run()) { - return {}; + return nullopt; } Response response; @@ -450,7 +483,7 @@ optional<int64_t> OfflineDatabase::hasTile(const Resource::TileData& tile) { size.bind(5, tile.z); if (!size.run()) { - return {}; + return nullopt; } return size.get<optional<int64_t>>(0); @@ -559,23 +592,30 @@ bool OfflineDatabase::putTile(const Resource::TileData& tile, return true; } -std::vector<OfflineRegion> OfflineDatabase::listRegions() { +std::vector<OfflineRegion> OfflineDatabase::listRegions() try { mapbox::sqlite::Query query{ getStatement("SELECT id, definition, description FROM regions") }; - std::vector<OfflineRegion> result; - while (query.run()) { - result.push_back(OfflineRegion( - query.get<int64_t>(0), - decodeOfflineRegionDefinition(query.get<std::string>(1)), - query.get<std::vector<uint8_t>>(2))); + const auto id = query.get<int64_t>(0); + const auto definition = query.get<std::string>(1); + const auto description = query.get<std::vector<uint8_t>>(2); + try { + OfflineRegion region(id, decodeOfflineRegionDefinition(definition), description); + result.emplace_back(std::move(region)); + } catch (const std::exception& ex) { + // Catch errors from malformed offline region definitions + // and skip them. + Log::Error(Event::General, "%s", ex.what()); + } } - return result; +} catch (const mapbox::sqlite::Exception& ex) { + handleError(ex, "list regions"); + return {}; } -OfflineRegion OfflineDatabase::createRegion(const OfflineRegionDefinition& definition, - const OfflineRegionMetadata& metadata) { +optional<OfflineRegion> OfflineDatabase::createRegion(const OfflineRegionDefinition& definition, + const OfflineRegionMetadata& metadata) try { // clang-format off mapbox::sqlite::Query query{ getStatement( "INSERT INTO regions (definition, description) " @@ -585,11 +625,13 @@ OfflineRegion OfflineDatabase::createRegion(const OfflineRegionDefinition& defin query.bind(1, encodeOfflineRegionDefinition(definition)); query.bindBlob(2, metadata); query.run(); - return OfflineRegion(query.lastInsertRowId(), definition, metadata); +} catch (const mapbox::sqlite::Exception& ex) { + handleError(ex, "create region"); + return nullopt; } -OfflineRegionMetadata OfflineDatabase::updateMetadata(const int64_t regionID, const OfflineRegionMetadata& metadata) { +optional<OfflineRegionMetadata> OfflineDatabase::updateMetadata(const int64_t regionID, const OfflineRegionMetadata& metadata) try { // clang-format off mapbox::sqlite::Query query{ getStatement( "UPDATE regions SET description = ?1 " @@ -600,9 +642,12 @@ OfflineRegionMetadata OfflineDatabase::updateMetadata(const int64_t regionID, co query.run(); return metadata; +} catch (const mapbox::sqlite::Exception& ex) { + handleError(ex, "update region metadata"); + return nullopt; } -void OfflineDatabase::deleteRegion(OfflineRegion&& region) { +void OfflineDatabase::deleteRegion(OfflineRegion&& region) try { { mapbox::sqlite::Query query{ getStatement("DELETE FROM regions WHERE id = ?") }; query.bind(1, region.getID()); @@ -610,13 +655,17 @@ void OfflineDatabase::deleteRegion(OfflineRegion&& region) { } evict(0); + assert(db); db->exec("PRAGMA incremental_vacuum"); // Ensure that the cached offlineTileCount value is recalculated. offlineMapboxTileCount = {}; +} catch (const mapbox::sqlite::Exception& ex) { + handleError(ex, "delete region"); + return; } -optional<std::pair<Response, uint64_t>> OfflineDatabase::getRegionResource(int64_t regionID, const Resource& resource) { +optional<std::pair<Response, uint64_t>> OfflineDatabase::getRegionResource(int64_t regionID, const Resource& resource) try { auto response = getInternal(resource); if (response) { @@ -624,9 +673,12 @@ optional<std::pair<Response, uint64_t>> OfflineDatabase::getRegionResource(int64 } return response; +} catch (const mapbox::sqlite::Exception& ex) { + handleError(ex, "read region resource"); + return nullopt; } -optional<int64_t> OfflineDatabase::hasRegionResource(int64_t regionID, const Resource& resource) { +optional<int64_t> OfflineDatabase::hasRegionResource(int64_t regionID, const Resource& resource) try { auto response = hasInternal(resource); if (response) { @@ -634,29 +686,52 @@ optional<int64_t> OfflineDatabase::hasRegionResource(int64_t regionID, const Res } return response; +} catch (const mapbox::sqlite::Exception& ex) { + handleError(ex, "query region resource"); + return nullopt; } -uint64_t OfflineDatabase::putRegionResource(int64_t regionID, const Resource& resource, const Response& response) { +uint64_t OfflineDatabase::putRegionResource(int64_t regionID, + const Resource& resource, + const Response& response) try { + if (!db) { + initialize(); + } mapbox::sqlite::Transaction transaction(*db); auto size = putRegionResourceInternal(regionID, resource, response); transaction.commit(); return size; +} catch (const mapbox::sqlite::Exception& ex) { + handleError(ex, "write region resource"); + return 0; } -void OfflineDatabase::putRegionResources(int64_t regionID, const std::list<std::tuple<Resource, Response>>& resources, OfflineRegionStatus& status) { +void OfflineDatabase::putRegionResources(int64_t regionID, + const std::list<std::tuple<Resource, Response>>& resources, + OfflineRegionStatus& status) try { + if (!db) { + initialize(); + } mapbox::sqlite::Transaction transaction(*db); + // Accumulate all statistics locally first before adding them to the OfflineRegionStatus object + // to ensure correctness when the transaction fails. + uint64_t completedResourceCount = 0; + uint64_t completedResourceSize = 0; + uint64_t completedTileCount = 0; + uint64_t completedTileSize = 0; + for (const auto& elem : resources) { const auto& resource = std::get<0>(elem); const auto& response = std::get<1>(elem); try { uint64_t resourceSize = putRegionResourceInternal(regionID, resource, response); - status.completedResourceCount++; - status.completedResourceSize += resourceSize; + completedResourceCount++; + completedResourceSize += resourceSize; if (resource.kind == Resource::Kind::Tile) { - status.completedTileCount += 1; - status.completedTileSize += resourceSize; + completedTileCount += 1; + completedTileSize += resourceSize; } } catch (const MapboxTileLimitExceededException&) { // Commit the rest of the batch and retrow @@ -667,6 +742,13 @@ void OfflineDatabase::putRegionResources(int64_t regionID, const std::list<std:: // Commit the completed batch transaction.commit(); + + status.completedResourceCount += completedResourceCount; + status.completedResourceSize += completedResourceSize; + status.completedTileCount += completedTileCount; + status.completedTileSize += completedTileSize; +} catch (const mapbox::sqlite::Exception& ex) { + handleError(ex, "write region resources"); } uint64_t OfflineDatabase::putRegionResourceInternal(int64_t regionID, const Resource& resource, const Response& response) { @@ -755,7 +837,7 @@ bool OfflineDatabase::markUsed(int64_t regionID, const Resource& resource) { mapbox::sqlite::Query selectQuery{ getStatement( "SELECT region_id " "FROM region_resources, resources " - "WHERE region_id != ?1 " + "WHERE region_id != ?1 " " AND resources.url = ?2 " "LIMIT 1 ") }; // clang-format on @@ -766,15 +848,18 @@ bool OfflineDatabase::markUsed(int64_t regionID, const Resource& resource) { } } -OfflineRegionDefinition OfflineDatabase::getRegionDefinition(int64_t regionID) { +optional<OfflineRegionDefinition> OfflineDatabase::getRegionDefinition(int64_t regionID) try { mapbox::sqlite::Query query{ getStatement("SELECT definition FROM regions WHERE id = ?1") }; query.bind(1, regionID); query.run(); return decodeOfflineRegionDefinition(query.get<std::string>(0)); +} catch (const mapbox::sqlite::Exception& ex) { + handleError(ex, "load region"); + return nullopt; } -OfflineRegionStatus OfflineDatabase::getRegionCompletedStatus(int64_t regionID) { +optional<OfflineRegionStatus> OfflineDatabase::getRegionCompletedStatus(int64_t regionID) try { OfflineRegionStatus result; std::tie(result.completedResourceCount, result.completedResourceSize) @@ -786,6 +871,9 @@ OfflineRegionStatus OfflineDatabase::getRegionCompletedStatus(int64_t regionID) result.completedResourceSize += result.completedTileSize; return result; +} catch (const mapbox::sqlite::Exception& ex) { + handleError(ex, "get region status"); + return nullopt; } std::pair<int64_t, int64_t> OfflineDatabase::getCompletedResourceCountAndSize(int64_t regionID) { @@ -920,7 +1008,7 @@ bool OfflineDatabase::offlineMapboxTileCountLimitExceeded() { return getOfflineMapboxTileCount() >= offlineMapboxTileCountLimit; } -uint64_t OfflineDatabase::getOfflineMapboxTileCount() { +uint64_t OfflineDatabase::getOfflineMapboxTileCount() try { // Calculating this on every call would be much simpler than caching and // manually updating the value, but it would make offline downloads an O(n²) // operation, because the database query below involves an index scan of @@ -942,6 +1030,9 @@ uint64_t OfflineDatabase::getOfflineMapboxTileCount() { offlineMapboxTileCount = query.get<int64_t>(0); return *offlineMapboxTileCount; +} catch (const mapbox::sqlite::Exception& ex) { + handleError(ex, "get offline Mapbox tile count"); + return std::numeric_limits<uint64_t>::max(); } bool OfflineDatabase::exceedsOfflineMapboxTileCountLimit(const Resource& resource) { diff --git a/platform/default/mbgl/storage/offline_database.hpp b/platform/default/mbgl/storage/offline_database.hpp index 38eb3783ba..cdad11b79e 100644 --- a/platform/default/mbgl/storage/offline_database.hpp +++ b/platform/default/mbgl/storage/offline_database.hpp @@ -18,6 +18,7 @@ namespace sqlite { class Database; class Statement; class Query; +class Exception; } // namespace sqlite } // namespace mapbox @@ -26,6 +27,10 @@ namespace mbgl { class Response; class TileID; +namespace util { +struct IOException; +} // namespace util + struct MapboxTileLimitExceededException : util::Exception { MapboxTileLimitExceededException() : util::Exception("Mapbox tile limit exceeded") {} }; @@ -44,10 +49,10 @@ public: std::vector<OfflineRegion> listRegions(); - OfflineRegion createRegion(const OfflineRegionDefinition&, - const OfflineRegionMetadata&); + optional<OfflineRegion> createRegion(const OfflineRegionDefinition&, + const OfflineRegionMetadata&); - OfflineRegionMetadata updateMetadata(const int64_t regionID, const OfflineRegionMetadata&); + optional<OfflineRegionMetadata> updateMetadata(const int64_t regionID, const OfflineRegionMetadata&); void deleteRegion(OfflineRegion&&); @@ -57,8 +62,8 @@ public: uint64_t putRegionResource(int64_t regionID, const Resource&, const Response&); void putRegionResources(int64_t regionID, const std::list<std::tuple<Resource, Response>>&, OfflineRegionStatus&); - OfflineRegionDefinition getRegionDefinition(int64_t regionID); - OfflineRegionStatus getRegionCompletedStatus(int64_t regionID); + optional<OfflineRegionDefinition> getRegionDefinition(int64_t regionID); + optional<OfflineRegionStatus> getRegionCompletedStatus(int64_t regionID); void setOfflineMapboxTileCountLimit(uint64_t); uint64_t getOfflineMapboxTileCountLimit(); @@ -67,12 +72,15 @@ public: bool exceedsOfflineMapboxTileCountLimit(const Resource&); private: - int userVersion(); - void ensureSchema(); + void initialize(); + void handleError(const mapbox::sqlite::Exception&, const char* action); + void handleError(const util::IOException&, const char* action); + void removeExisting(); void removeOldCacheTable(); - void migrateToVersion3(); + void createSchema(); void migrateToVersion5(); + void migrateToVersion3(); void migrateToVersion6(); mapbox::sqlite::Statement& getStatement(const char *); diff --git a/platform/default/mbgl/storage/offline_download.cpp b/platform/default/mbgl/storage/offline_download.cpp index 4da51db655..179d2d5f57 100644 --- a/platform/default/mbgl/storage/offline_download.cpp +++ b/platform/default/mbgl/storage/offline_download.cpp @@ -62,39 +62,44 @@ OfflineRegionStatus OfflineDownload::getStatus() const { return status; } - OfflineRegionStatus result = offlineDatabase.getRegionCompletedStatus(id); + auto result = offlineDatabase.getRegionCompletedStatus(id); + if (!result) { + // We can't find this offline region because the database is unavailable, or the download + // does not exist. + return {}; + } - result.requiredResourceCount++; + result->requiredResourceCount++; optional<Response> styleResponse = offlineDatabase.get(Resource::style(definition.styleURL)); if (!styleResponse) { - return result; + return *result; } style::Parser parser; parser.parse(*styleResponse->data); - result.requiredResourceCountIsPrecise = true; + result->requiredResourceCountIsPrecise = true; for (const auto& source : parser.sources) { SourceType type = source->getType(); auto handleTiledSource = [&] (const variant<std::string, Tileset>& urlOrTileset, const uint16_t tileSize) { if (urlOrTileset.is<Tileset>()) { - result.requiredResourceCount += + result->requiredResourceCount += definition.tileCount(type, tileSize, urlOrTileset.get<Tileset>().zoomRange); } else { - result.requiredResourceCount += 1; + result->requiredResourceCount += 1; const auto& url = urlOrTileset.get<std::string>(); optional<Response> sourceResponse = offlineDatabase.get(Resource::source(url)); if (sourceResponse) { style::conversion::Error error; optional<Tileset> tileset = style::conversion::convertJSON<Tileset>(*sourceResponse->data, error); if (tileset) { - result.requiredResourceCount += + result->requiredResourceCount += definition.tileCount(type, tileSize, (*tileset).zoomRange); } } else { - result.requiredResourceCountIsPrecise = false; + result->requiredResourceCountIsPrecise = false; } } }; @@ -121,7 +126,7 @@ OfflineRegionStatus OfflineDownload::getStatus() const { case SourceType::GeoJSON: { const auto& geojsonSource = *source->as<GeoJSONSource>(); if (geojsonSource.getURL()) { - result.requiredResourceCount += 1; + result->requiredResourceCount += 1; } break; } @@ -129,7 +134,7 @@ OfflineRegionStatus OfflineDownload::getStatus() const { case SourceType::Image: { const auto& imageSource = *source->as<ImageSource>(); if (imageSource.getURL()) { - result.requiredResourceCount += 1; + result->requiredResourceCount += 1; } break; } @@ -142,14 +147,14 @@ OfflineRegionStatus OfflineDownload::getStatus() const { } if (!parser.glyphURL.empty()) { - result.requiredResourceCount += parser.fontStacks().size() * GLYPH_RANGES_PER_FONT_STACK; + result->requiredResourceCount += parser.fontStacks().size() * GLYPH_RANGES_PER_FONT_STACK; } if (!parser.spriteURL.empty()) { - result.requiredResourceCount += 2; + result->requiredResourceCount += 2; } - return result; + return *result; } void OfflineDownload::activateDownload() { diff --git a/platform/default/sqlite3.cpp b/platform/default/sqlite3.cpp index 1a6045a9a8..fed5a3a185 100644 --- a/platform/default/sqlite3.cpp +++ b/platform/default/sqlite3.cpp @@ -8,11 +8,38 @@ #include <chrono> #include <experimental/optional> +#include <mbgl/util/traits.hpp> #include <mbgl/util/logging.hpp> namespace mapbox { namespace sqlite { +static_assert(mbgl::underlying_type(ResultCode::OK) == SQLITE_OK, "error"); +static_assert(mbgl::underlying_type(ResultCode::Error) == SQLITE_ERROR, "error"); +static_assert(mbgl::underlying_type(ResultCode::Internal) == SQLITE_INTERNAL, "error"); +static_assert(mbgl::underlying_type(ResultCode::Perm) == SQLITE_PERM, "error"); +static_assert(mbgl::underlying_type(ResultCode::Abort) == SQLITE_ABORT, "error"); +static_assert(mbgl::underlying_type(ResultCode::Busy) == SQLITE_BUSY, "error"); +static_assert(mbgl::underlying_type(ResultCode::Locked) == SQLITE_LOCKED, "error"); +static_assert(mbgl::underlying_type(ResultCode::NoMem) == SQLITE_NOMEM, "error"); +static_assert(mbgl::underlying_type(ResultCode::ReadOnly) == SQLITE_READONLY, "error"); +static_assert(mbgl::underlying_type(ResultCode::Interrupt) == SQLITE_INTERRUPT, "error"); +static_assert(mbgl::underlying_type(ResultCode::IOErr) == SQLITE_IOERR, "error"); +static_assert(mbgl::underlying_type(ResultCode::Corrupt) == SQLITE_CORRUPT, "error"); +static_assert(mbgl::underlying_type(ResultCode::NotFound) == SQLITE_NOTFOUND, "error"); +static_assert(mbgl::underlying_type(ResultCode::Full) == SQLITE_FULL, "error"); +static_assert(mbgl::underlying_type(ResultCode::CantOpen) == SQLITE_CANTOPEN, "error"); +static_assert(mbgl::underlying_type(ResultCode::Protocol) == SQLITE_PROTOCOL, "error"); +static_assert(mbgl::underlying_type(ResultCode::Schema) == SQLITE_SCHEMA, "error"); +static_assert(mbgl::underlying_type(ResultCode::TooBig) == SQLITE_TOOBIG, "error"); +static_assert(mbgl::underlying_type(ResultCode::Constraint) == SQLITE_CONSTRAINT, "error"); +static_assert(mbgl::underlying_type(ResultCode::Mismatch) == SQLITE_MISMATCH, "error"); +static_assert(mbgl::underlying_type(ResultCode::Misuse) == SQLITE_MISUSE, "error"); +static_assert(mbgl::underlying_type(ResultCode::NoLFS) == SQLITE_NOLFS, "error"); +static_assert(mbgl::underlying_type(ResultCode::Auth) == SQLITE_AUTH, "error"); +static_assert(mbgl::underlying_type(ResultCode::Range) == SQLITE_RANGE, "error"); +static_assert(mbgl::underlying_type(ResultCode::NotADB) == SQLITE_NOTADB, "error"); + class DatabaseImpl { public: DatabaseImpl(sqlite3* db_) @@ -24,7 +51,7 @@ public: { const int error = sqlite3_close(db); if (error != SQLITE_OK) { - mbgl::Log::Error(mbgl::Event::Database, "%s (Code %i)", sqlite3_errmsg(db), error); + mbgl::Log::Error(mbgl::Event::Database, error, "Failed to close database: %s", sqlite3_errmsg(db)); } } @@ -66,11 +93,8 @@ public: template <typename T> using optional = std::experimental::optional<T>; -static void errorLogCallback(void *, const int err, const char *msg) { - mbgl::Log::Record(mbgl::EventSeverity::Info, mbgl::Event::Database, err, "%s", msg); -} - -const static bool sqliteVersionCheck __attribute__((unused)) = []() { +__attribute__((constructor)) +static void initalize() { if (sqlite3_libversion_number() / 1000000 != SQLITE_VERSION_NUMBER / 1000000) { char message[96]; snprintf(message, 96, @@ -79,15 +103,17 @@ const static bool sqliteVersionCheck __attribute__((unused)) = []() { throw std::runtime_error(message); } +#ifndef NDEBUG // Enable SQLite logging before initializing the database. - sqlite3_config(SQLITE_CONFIG_LOG, errorLogCallback, nullptr); - - return true; -}(); + sqlite3_config(SQLITE_CONFIG_LOG, [](void *, const int err, const char *msg) { + mbgl::Log::Record(mbgl::EventSeverity::Debug, mbgl::Event::Database, err, "%s", msg); + }, nullptr); +#endif +} mapbox::util::variant<Database, Exception> Database::tryOpen(const std::string &filename, int flags) { sqlite3* db = nullptr; - const int error = sqlite3_open_v2(filename.c_str(), &db, flags, nullptr); + const int error = sqlite3_open_v2(filename.c_str(), &db, flags | SQLITE_OPEN_URI, nullptr); if (error != SQLITE_OK) { const auto message = sqlite3_errmsg(db); return Exception { error, message }; diff --git a/platform/qt/src/sqlite3.cpp b/platform/qt/src/sqlite3.cpp index 2ca09fd3ad..96cee5fff8 100644 --- a/platform/qt/src/sqlite3.cpp +++ b/platform/qt/src/sqlite3.cpp @@ -23,13 +23,6 @@ namespace mapbox { namespace sqlite { -// https://www.sqlite.org/rescode.html#ok -static_assert(mbgl::underlying_type(ResultCode::OK) == 0, "error"); -// https://www.sqlite.org/rescode.html#cantopen -static_assert(mbgl::underlying_type(ResultCode::CantOpen) == 14, "error"); -// https://www.sqlite.org/rescode.html#notadb -static_assert(mbgl::underlying_type(ResultCode::NotADB) == 26, "error"); - void checkQueryError(const QSqlQuery& query) { QSqlError lastError = query.lastError(); if (lastError.type() != QSqlError::NoError) { @@ -114,6 +107,11 @@ mapbox::util::variant<Database, Exception> Database::tryOpen(const std::string & connectOptions.append("QSQLITE_OPEN_READONLY"); } + if (filename.compare(0, 5, "file:") == 0) { + if (!connectOptions.isEmpty()) connectOptions.append(';'); + connectOptions.append("QSQLITE_OPEN_URI"); + } + db.setConnectOptions(connectOptions); db.setDatabaseName(QString(filename.c_str())); |