From fbe047a35fff100cb5f42d10984bccde137f586e Mon Sep 17 00:00:00 2001 From: jacqueline Date: Wed, 26 Apr 2023 15:21:32 +1000 Subject: [PATCH] Add some basic data and retrieval --- src/database/CMakeLists.txt | 2 +- src/database/database.cpp | 59 ++++++++++++++------------ src/database/env_esp.cpp | 2 +- src/database/include/database.hpp | 23 +++++++++- src/database/include/file_gatherer.hpp | 11 ++--- src/database/include/tag_processor.hpp | 13 +++++- src/database/tag_processor.cpp | 15 +++++++ src/main/app_console.cpp | 54 ++++++++++++++++++++++- src/main/app_console.hpp | 4 +- src/main/main.cpp | 16 ++++--- 10 files changed, 153 insertions(+), 46 deletions(-) create mode 100644 src/database/tag_processor.cpp diff --git a/src/database/CMakeLists.txt b/src/database/CMakeLists.txt index 01365bf7..3f9ca6fb 100644 --- a/src/database/CMakeLists.txt +++ b/src/database/CMakeLists.txt @@ -1,5 +1,5 @@ idf_component_register( - SRCS "env_esp.cpp" "database.cpp" + SRCS "env_esp.cpp" "database.cpp" "tag_processor.cpp" INCLUDE_DIRS "include" REQUIRES "result" "span" "esp_psram" "fatfs") diff --git a/src/database/database.cpp b/src/database/database.cpp index cfac77af..b677f4ba 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -4,6 +4,10 @@ #include "ff.h" #include "leveldb/cache.h" +#include "file_gatherer.hpp" +#include "leveldb/iterator.h" +#include "leveldb/slice.h" +#include "tag_processor.hpp" #include "env_esp.hpp" #include "leveldb/options.h" @@ -38,38 +42,37 @@ Database::Database(leveldb::DB* db, leveldb::Cache* cache) Database::~Database() {} -FRESULT scan_files(const std::string &path) { - FRESULT res; - FF_DIR dir; - static FILINFO fno; - - res = f_opendir(&dir, path.c_str()); - if (res == FR_OK) { - for (;;) { - res = f_readdir(&dir, &fno); - if (res != FR_OK || fno.fname[0] == 0) break; - if (fno.fname[0] == '.') continue; - if (fno.fattrib & AM_DIR) { - std::string new_path = path + "/" + fno.fname; - res = scan_files(new_path); - if (res != FR_OK) break; - } else { - ESP_LOGI(kTag, "found %s", fno.fname); - } - } - f_closedir(&dir); - } - - return res; +auto Database::Initialise() -> 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()); } -auto Database::Initialise() -> void { - // TODO(jacqueline): Abstractions lol - scan_files("/"); +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::Update() -> void { - // TODO(jacqueline): Incremental updates! +auto Iterator::Next() -> std::optional { + if (!it_->Valid()) { + return {}; + } + std::string ret = it_->key().ToString(); + it_->Next(); + return ret; } } // namespace database diff --git a/src/database/env_esp.cpp b/src/database/env_esp.cpp index 45129a36..363421a7 100644 --- a/src/database/env_esp.cpp +++ b/src/database/env_esp.cpp @@ -465,7 +465,7 @@ void EspEnv::Schedule( if (!started_background_thread_) { started_background_thread_ = true; xTaskCreate(reinterpret_cast(BackgroundThreadEntryPoint), - "LVL_ONEOFF", 2048, reinterpret_cast(this), 3, NULL); + "LVL_ONEOFF", 16 * 1024, reinterpret_cast(this), 3, NULL); } BackgroundWorkItem item(background_work_function, background_work_arg); diff --git a/src/database/include/database.hpp b/src/database/include/database.hpp index b9df5fd4..cb1437df 100644 --- a/src/database/include/database.hpp +++ b/src/database/include/database.hpp @@ -1,13 +1,18 @@ #pragma once +#include #include +#include #include "leveldb/cache.h" #include "leveldb/db.h" +#include "leveldb/iterator.h" #include "result.hpp" namespace database { +class Iterator; + class Database { public: enum DatabaseError { @@ -18,7 +23,10 @@ class Database { ~Database(); auto Initialise() -> void; - auto Update() -> void; + auto ByTitle() -> Iterator; + + Database(const Database&) = delete; + Database& operator=(const Database&) = delete; private: std::unique_ptr db_; @@ -27,4 +35,17 @@ class Database { Database(leveldb::DB* db, leveldb::Cache* cache); }; +class Iterator { + public: + explicit Iterator(leveldb::Iterator *it) : it_(it) {} + + auto Next() -> std::optional; + + Iterator(const Iterator&) = delete; + Iterator& operator=(const Iterator&) = delete; + + private: + std::unique_ptr it_; +}; + } // namespace database diff --git a/src/database/include/file_gatherer.hpp b/src/database/include/file_gatherer.hpp index 47d40f88..7cf00b41 100644 --- a/src/database/include/file_gatherer.hpp +++ b/src/database/include/file_gatherer.hpp @@ -12,7 +12,7 @@ namespace database { static_assert(sizeof(TCHAR) == sizeof(char), "TCHAR must be CHAR"); template -auto FindFiles(FATFS* fs, const std::string& root, Callback cb) -> void { +auto FindFiles(const std::string& root, Callback cb) -> void { std::deque to_explore; to_explore.push_back(root); @@ -20,7 +20,7 @@ auto FindFiles(FATFS* fs, const std::string& root, Callback cb) -> void { std::string next_path_str = to_explore.front(); const TCHAR* next_path = static_cast(next_path_str.c_str()); - DIR dir; + FF_DIR dir; FRESULT res = f_opendir(&dir, next_path); if (res != FR_OK) { // TODO: log. @@ -30,10 +30,10 @@ auto FindFiles(FATFS* fs, const std::string& root, Callback cb) -> void { for (;;) { FILINFO info; res = f_readdir(&dir, &info); - if (info.fname == NULL) { + if (res != FR_OK || info.fname[0] == 0) { // No more files in the directory. break; - } else if (info.fattrib & (AM_HID | AM_SYS)) { + } else if (info.fattrib & (AM_HID | AM_SYS) || info.fname[0] == '.') { // System or hidden file. Ignore it and move on. continue; } else { @@ -45,7 +45,8 @@ auto FindFiles(FATFS* fs, const std::string& root, Callback cb) -> void { to_explore.push_back(full_path.str()); } else { // This is a file! Let the callback know about it. - std::invoke(cb, full_path.str(), info); + //std::invoke(cb, full_path.str(), info); + std::invoke(cb, full_path.str()); } } } diff --git a/src/database/include/tag_processor.hpp b/src/database/include/tag_processor.hpp index 0257fc92..88c95b61 100644 --- a/src/database/include/tag_processor.hpp +++ b/src/database/include/tag_processor.hpp @@ -1,3 +1,14 @@ #pragma once -namespace database {} // namespace database +#include + +namespace database { + +struct FileInfo { + bool is_playable; + std::string title; +}; + +auto GetInfo(const std::string &path, FileInfo *out) -> bool; + +} // namespace database diff --git a/src/database/tag_processor.cpp b/src/database/tag_processor.cpp new file mode 100644 index 00000000..f2d520a4 --- /dev/null +++ b/src/database/tag_processor.cpp @@ -0,0 +1,15 @@ +#include "tag_processor.hpp" + +namespace database { + +auto GetInfo(const std::string &path, FileInfo *out) -> bool { + // TODO(jacqueline): bring in taglib for this + if (path.ends_with(".mp3")) { + out->is_playable = true; + out->title = path.substr(0, path.size() - 4); + return true; + } + return false; +} + +} // namespace database diff --git a/src/main/app_console.cpp b/src/main/app_console.cpp index 40159f4e..859700f4 100644 --- a/src/main/app_console.cpp +++ b/src/main/app_console.cpp @@ -9,7 +9,9 @@ #include #include "audio_playback.hpp" +#include "database.hpp" #include "esp_console.h" +#include "esp_log.h" namespace console { @@ -145,7 +147,55 @@ void RegisterAudioStatus() { esp_console_cmd_register(&cmd); } -AppConsole::AppConsole(audio::AudioPlayback* playback) : playback_(playback) { +int CmdDbInit(int argc, char** argv) { + static const std::string usage = "usage: db_init"; + if (argc != 1) { + std::cout << usage << std::endl; + return 1; + } + + sInstance->database_->Initialise(); + + return 0; +} + +void RegisterDbInit() { + esp_console_cmd_t cmd{.command = "db_init", + .help = "scans for playable files and adds them to the database", + .hint = NULL, + .func = &CmdDbInit, + .argtable = NULL}; + esp_console_cmd_register(&cmd); +} + +int CmdDbTitles(int argc, char** argv) { + static const std::string usage = "usage: db_titles"; + if (argc != 1) { + std::cout << usage << std::endl; + return 1; + } + + database::Iterator it = sInstance->database_->ByTitle(); + while (true) { + std::optional title = it.Next(); + if (!title) { + break; + } + std::cout << *title << std::endl; + } + return 0; +} + +void RegisterDbTitles() { + esp_console_cmd_t cmd{.command = "db_titles", + .help = "lists titles of ALL songs in the database", + .hint = NULL, + .func = &CmdDbTitles, + .argtable = NULL}; + esp_console_cmd_register(&cmd); +} + +AppConsole::AppConsole(audio::AudioPlayback* playback, database::Database *database) : playback_(playback), database_(database) { sInstance = this; } AppConsole::~AppConsole() { @@ -158,6 +208,8 @@ auto AppConsole::RegisterExtraComponents() -> void { RegisterToggle(); RegisterVolume(); RegisterAudioStatus(); + RegisterDbInit(); + RegisterDbTitles(); } } // namespace console diff --git a/src/main/app_console.hpp b/src/main/app_console.hpp index f94bcb51..fcefd4d4 100644 --- a/src/main/app_console.hpp +++ b/src/main/app_console.hpp @@ -4,16 +4,18 @@ #include "audio_playback.hpp" #include "console.hpp" +#include "database.hpp" #include "storage.hpp" namespace console { class AppConsole : public Console { public: - explicit AppConsole(audio::AudioPlayback* playback); + explicit AppConsole(audio::AudioPlayback* playback, database::Database *database); virtual ~AppConsole(); audio::AudioPlayback* playback_; + database::Database *database_; protected: virtual auto RegisterExtraComponents() -> void; diff --git a/src/main/main.cpp b/src/main/main.cpp index 312018cb..6ef7c61b 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -38,6 +38,7 @@ static const char* TAG = "MAIN"; void db_main(void* whatever) { + database::Database **arg_db = reinterpret_cast(whatever); ESP_LOGI(TAG, "Init database"); std::unique_ptr db; auto db_res = database::Database::Open(); @@ -48,13 +49,13 @@ void db_main(void* whatever) { ESP_LOGI(TAG, "database good :)"); } - vTaskDelay(pdMS_TO_TICKS(2000)); + *arg_db = db.get(); - db->Initialise(); + db->ByTitle(); - vTaskDelay(pdMS_TO_TICKS(2000)); - - db.reset(); + while (1) { + vTaskDelay(portMAX_DELAY); + } vTaskDelete(NULL); } @@ -89,7 +90,8 @@ extern "C" void app_main(void) { StaticTask_t database_task_buffer = {}; StackType_t* database_stack = reinterpret_cast( heap_caps_malloc(db_stack_size, MALLOC_CAP_SPIRAM)); - xTaskCreateStatic(&db_main, "LEVELDB", db_stack_size, NULL, 1, database_stack, + database::Database *db; + xTaskCreateStatic(&db_main, "LEVELDB", db_stack_size, &db, 1, database_stack, &database_task_buffer); ESP_LOGI(TAG, "Init touch wheel"); @@ -110,7 +112,7 @@ extern "C" void app_main(void) { vTaskDelay(pdMS_TO_TICKS(1000)); ESP_LOGI(TAG, "Launch console"); - console::AppConsole console(playback.get()); + console::AppConsole console(playback.get(), db); console.Launch(); uint8_t prev_position = 0;