From 5d7cbec34cd5e473d5768b39054d99bc72ddad62 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Thu, 27 Apr 2023 12:55:30 +1000 Subject: Move DB interactions to a background thread --- src/database/database.cpp | 128 +++++++++++++++++++++++++++++++--------------- 1 file changed, 88 insertions(+), 40 deletions(-) (limited to 'src/database/database.cpp') diff --git a/src/database/database.cpp b/src/database/database.cpp index b677f4ba..8ca72771 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -4,75 +4,123 @@ #include "ff.h" #include "leveldb/cache.h" +#include "db_task.hpp" +#include "env_esp.hpp" #include "file_gatherer.hpp" #include "leveldb/iterator.h" +#include "leveldb/options.h" #include "leveldb/slice.h" +#include "result.hpp" #include "tag_processor.hpp" -#include "env_esp.hpp" -#include "leveldb/options.h" namespace database { static SingletonEnv sEnv; -static const char *kTag = "DB"; +static const char* kTag = "DB"; + +static std::atomic sIsDbOpen(false); auto Database::Open() -> cpp::result { - leveldb::DB* db; - leveldb::Cache* 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_size = 512; - - auto status = leveldb::DB::Open(options, "/.db", &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); + // TODO(jacqueline): Why isn't compare_and_exchange_* available? + if (sIsDbOpen.exchange(true)) { + return cpp::fail(DatabaseError::ALREADY_OPEN); } - return new Database(db, cache); + if (!StartDbTask()) { + return cpp::fail(DatabaseError::ALREADY_OPEN); + } + + return RunOnDbTask>( + []() -> cpp::result { + leveldb::DB* db; + leveldb::Cache* 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_size = 512; + + auto status = leveldb::DB::Open(options, "/.db", &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, "Database opened successfully"); + return new Database(db, cache); + }) + .get(); } Database::Database(leveldb::DB* db, leveldb::Cache* cache) : db_(db), cache_(cache) {} -Database::~Database() {} +Database::~Database() { + QuitDbTask(); + sIsDbOpen.store(false); +} -auto Database::Initialise() -> void { - leveldb::WriteOptions opt; - opt.sync = true; - FindFiles("", [&](const std::string &path) { +template +auto IterateAndParse(leveldb::Iterator* it, std::size_t limit, Parser p) + -> void { + for (int i = 0; i < limit; i++) { + if (!it->Valid()) { + break; + } + std::invoke(p, it->key(), it->value()); + it->Next(); + } +} + +auto Database::Populate() -> std::future { + return RunOnDbTask([&]() -> void { + leveldb::WriteOptions opt; + opt.sync = true; + FindFiles("", [&](const std::string& path) { ESP_LOGI(kTag, "considering %s", path.c_str()); FileInfo info; if (GetInfo(path, &info)) { ESP_LOGI(kTag, "added as '%s'", info.title.c_str()); db_->Put(opt, "title:" + info.title, path); } + }); + db_->Put(opt, "title:coolkeywithoutval", leveldb::Slice()); }); - db_->Put(opt, "title:coolkeywithoutval", leveldb::Slice()); } -auto Database::ByTitle() -> Iterator { - leveldb::Iterator *it = db_->NewIterator(leveldb::ReadOptions()); - it->Seek("title:"); - while (it->Valid()) { - ESP_LOGI(kTag, "%s : %s", it->key().ToString().c_str(), it->value().ToString().c_str()); - it->Next(); - } - return Iterator(it); +auto Database::GetSongs(std::size_t page_size) -> std::future> { + return RunOnDbTask>([&]() -> DbResult { + std::unique_ptr it( + db_->NewIterator(leveldb::ReadOptions())); + it->Seek("title:"); + std::vector results; + IterateAndParse(it.get(), page_size, + [&](const leveldb::Slice& key, const leveldb::Slice& val) { + Song s; + s.title = key.ToString(); + results.push_back(s); + }); + return DbResult(results, std::move(it)); + }); } -auto Iterator::Next() -> std::optional { - if (!it_->Valid()) { - return {}; - } - std::string ret = it_->key().ToString(); - it_->Next(); - return ret; +auto Database::GetMoreSongs(std::size_t page_size, DbResult continuation) + -> std::future> { + return RunOnDbTask>([&]() -> DbResult { + std::unique_ptr it(continuation.it()); + std::vector results; + IterateAndParse(it.get(), page_size, + [&](const leveldb::Slice& key, const leveldb::Slice& val) { + Song s; + s.title = key.ToString(); + results.push_back(s); + }); + return DbResult(results, std::move(it)); + }); } } // namespace database -- cgit v1.2.3