diff --git a/src/tangara/database/database.cpp b/src/tangara/database/database.cpp index e3f3df67..c543b941 100644 --- a/src/tangara/database/database.cpp +++ b/src/tangara/database/database.cpp @@ -24,6 +24,7 @@ #include "cppbor.h" #include "cppbor_parse.h" #include "database/index.hpp" +#include "debug.hpp" #include "esp_log.h" #include "esp_timer.h" #include "ff.h" @@ -60,7 +61,6 @@ static const char kKeyDbVersion[] = "schema_version"; static const char kKeyCustom[] = "U\0"; static const char kKeyCollator[] = "collator"; -static const char kKeyTrackId[] = "next_track_id"; static std::atomic sIsDbOpen(false); @@ -190,7 +190,10 @@ Database::Database(leveldb::DB* db, file_gatherer_(file_gatherer), tag_parser_(tag_parser), collator_(collator), - is_updating_(false) {} + is_updating_(false) { + dbCalculateNextTrackId(); + ESP_LOGI(kTag, "next track id is %lu", next_track_id_.load()); +} Database::~Database() { // Delete db_ first so that any outstanding background work finishes before @@ -492,24 +495,45 @@ auto Database::isUpdating() -> bool { return is_updating_; } -auto Database::dbMintNewTrackId() -> TrackId { - TrackId next_id = 1; - std::string val; - auto status = db_->Get(leveldb::ReadOptions(), kKeyTrackId, &val); - if (status.ok()) { - next_id = BytesToTrackId(val).value_or(next_id); - } else if (!status.IsNotFound()) { - // TODO(jacqueline): Handle this more. - ESP_LOGE(kTag, "failed to get next track id"); +auto Database::dbCalculateNextTrackId() -> void { + std::unique_ptr it{ + db_->NewIterator(leveldb::ReadOptions())}; + + // Track data entries are of the format 'D/trackid', where track ids are + // encoded as big-endian cbor types. They can therefore be compared through + // byte ordering, which means we can determine what the next id should be by + // looking at the larged track data record in the database. + std::string prefix = EncodeDataPrefix(); + std::string prefixPlusOne = prefix; + prefixPlusOne[prefixPlusOne.size() - 1]++; + + // Seek to just past the track data section. + it->Seek(prefixPlusOne); + if (!it->Valid()) { + next_track_id_ = 1; + return; } - if (!db_->Put(leveldb::WriteOptions(), kKeyTrackId, - TrackIdToBytes(next_id + 1)) - .ok()) { - ESP_LOGE(kTag, "failed to write next track id"); + // Go back to the last track data record. + it->Prev(); + if (!it->Valid() || !it->key().starts_with(prefix)) { + next_track_id_ = 1; + return; } - return next_id; + // Parse the track id back out of the key. + std::span key{it->key().data(), it->key().size()}; + auto id_part = key.subspan(prefix.size()); + if (id_part.empty()) { + next_track_id_ = 1; + return; + } + + next_track_id_ = BytesToTrackId(id_part).value_or(0) + 1; +} + +auto Database::dbMintNewTrackId() -> TrackId { + return next_track_id_++; } auto Database::dbEntomb(TrackId id, uint64_t hash) -> void { diff --git a/src/tangara/database/database.hpp b/src/tangara/database/database.hpp index c2e72568..2b385013 100644 --- a/src/tangara/database/database.hpp +++ b/src/tangara/database/database.hpp @@ -100,6 +100,7 @@ class Database { locale::ICollator& collator_; std::atomic is_updating_; + std::atomic next_track_id_; Database(leveldb::DB* db, leveldb::Cache* cache, @@ -107,6 +108,7 @@ class Database { ITagParser& tag_parser, locale::ICollator& collator); + auto dbCalculateNextTrackId() -> void; auto dbMintNewTrackId() -> TrackId; auto dbEntomb(TrackId track, uint64_t hash) -> void;