From c2dddba30a2fcf8a1f4d8d1d459d6270f8f1ce1d Mon Sep 17 00:00:00 2001 From: jacqueline Date: Sat, 14 Oct 2023 11:46:42 +1100 Subject: [PATCH] Add very basic db versioning --- src/database/database.cpp | 71 ++++++++++++++++++++++++++++++--------- 1 file changed, 56 insertions(+), 15 deletions(-) diff --git a/src/database/database.cpp b/src/database/database.cpp index f86b2c9b..6106693c 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -44,10 +44,33 @@ namespace database { static SingletonEnv sEnv; static const char* kTag = "DB"; -static const char kTrackIdKey[] = "next_track_id"; +static const char kDbPath[] = "/.tangara-db"; + +static const char kKeyDbVersion[] = "schema_version"; +static const uint8_t kCurrentDbVersion = 1; + +static const char kKeyTrackId[] = "next_track_id"; static std::atomic sIsDbOpen(false); +static auto CreateNewDatabase(leveldb::Options& options) -> leveldb::DB* { + Database::Destroy(); + leveldb::DB* db; + options.create_if_missing = true; + auto status = leveldb::DB::Open(options, kDbPath, &db); + if (!status.ok()) { + ESP_LOGE(kTag, "failed to open db, status %s", status.ToString().c_str()); + return nullptr; + } + auto version_str = std::to_string(kCurrentDbVersion); + status = db->Put(leveldb::WriteOptions{}, kKeyDbVersion, version_str); + if (!status.ok()) { + delete db; + return nullptr; + } + return db; +} + auto Database::Open(IFileGatherer& gatherer, ITagParser& parser) -> cpp::result { // TODO(jacqueline): Why isn't compare_and_exchange_* available? @@ -66,25 +89,43 @@ auto Database::Open(IFileGatherer& gatherer, ITagParser& parser) ->Dispatch>( [&]() -> cpp::result { leveldb::DB* db; - leveldb::Cache* cache = leveldb::NewLRUCache(24 * 1024); + std::unique_ptr cache{ + leveldb::NewLRUCache(24 * 1024)}; + leveldb::Options options; options.env = sEnv.env(); - options.create_if_missing = true; options.write_buffer_size = 48 * 1024; options.max_file_size = 32; - options.block_cache = cache; + options.block_cache = cache.get(); options.block_size = 512; - auto status = leveldb::DB::Open(options, "/.db", &db); + auto status = leveldb::DB::Open(options, kDbPath, &db); if (!status.ok()) { - delete cache; - ESP_LOGE(kTag, "failed to open db, status %s", - status.ToString().c_str()); - return cpp::fail(FAILED_TO_OPEN); + ESP_LOGI(kTag, "opening db failed. recreating."); + db = CreateNewDatabase(options); + if (db == nullptr) { + return cpp::fail(FAILED_TO_OPEN); + } + } + + std::string raw_version; + std::optional version{}; + status = + db->Get(leveldb::ReadOptions{}, kKeyDbVersion, &raw_version); + if (status.ok()) { + version = std::stoi(raw_version); + } + if (!version || *version != kCurrentDbVersion) { + ESP_LOGI(kTag, "db version missing or incorrect. recreating."); + delete db; + db = CreateNewDatabase(options); + if (db == nullptr) { + return cpp::fail(FAILED_TO_OPEN); + } } ESP_LOGI(kTag, "Database opened successfully"); - return new Database(db, cache, gatherer, parser, worker); + return new Database(db, cache.release(), gatherer, parser, worker); }) .get(); } @@ -92,7 +133,7 @@ auto Database::Open(IFileGatherer& gatherer, ITagParser& parser) auto Database::Destroy() -> void { leveldb::Options options; options.env = sEnv.env(); - leveldb::DestroyDB("/.db", options); + leveldb::DestroyDB(kDbPath, options); } Database::Database(leveldb::DB* db, @@ -412,7 +453,7 @@ template auto Database::GetPage(Continuation* c) auto Database::dbMintNewTrackId() -> TrackId { TrackId next_id = 1; std::string val; - auto status = db_->Get(leveldb::ReadOptions(), kTrackIdKey, &val); + auto status = db_->Get(leveldb::ReadOptions(), kKeyTrackId, &val); if (status.ok()) { next_id = BytesToTrackId(val).value_or(next_id); } else if (!status.IsNotFound()) { @@ -420,7 +461,7 @@ auto Database::dbMintNewTrackId() -> TrackId { ESP_LOGE(kTag, "failed to get next track id"); } - if (!db_->Put(leveldb::WriteOptions(), kTrackIdKey, + if (!db_->Put(leveldb::WriteOptions(), kKeyTrackId, TrackIdToBytes(next_id + 1)) .ok()) { ESP_LOGE(kTag, "failed to write next track id"); @@ -560,8 +601,8 @@ auto Database::dbGetPage(const Continuation& c) -> Result* { std::optional prev_page; if (c.forward) { - // We were going forwards, and now we want the previous page. Set the search - // key to the first result we saw, and mark that it's off by one. + // We were going forwards, and now we want the previous page. Set the + // search key to the first result we saw, and mark that it's off by one. prev_page = Continuation{ .prefix = c.prefix, .start_key = *first_key,