Add some basic data and retrieval

custom
jacqueline 2 years ago
parent 083f4011aa
commit fbe047a35f
  1. 2
      src/database/CMakeLists.txt
  2. 55
      src/database/database.cpp
  3. 2
      src/database/env_esp.cpp
  4. 23
      src/database/include/database.hpp
  5. 11
      src/database/include/file_gatherer.hpp
  6. 13
      src/database/include/tag_processor.hpp
  7. 15
      src/database/tag_processor.cpp
  8. 54
      src/main/app_console.cpp
  9. 4
      src/main/app_console.hpp
  10. 16
      src/main/main.cpp

@ -1,5 +1,5 @@
idf_component_register( idf_component_register(
SRCS "env_esp.cpp" "database.cpp" SRCS "env_esp.cpp" "database.cpp" "tag_processor.cpp"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
REQUIRES "result" "span" "esp_psram" "fatfs") REQUIRES "result" "span" "esp_psram" "fatfs")

@ -4,6 +4,10 @@
#include "ff.h" #include "ff.h"
#include "leveldb/cache.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 "env_esp.hpp"
#include "leveldb/options.h" #include "leveldb/options.h"
@ -38,38 +42,37 @@ Database::Database(leveldb::DB* db, leveldb::Cache* cache)
Database::~Database() {} Database::~Database() {}
FRESULT scan_files(const std::string &path) { auto Database::Initialise() -> void {
FRESULT res; leveldb::WriteOptions opt;
FF_DIR dir; opt.sync = true;
static FILINFO fno; FindFiles("", [&](const std::string &path) {
ESP_LOGI(kTag, "considering %s", path.c_str());
res = f_opendir(&dir, path.c_str()); FileInfo info;
if (res == FR_OK) { if (GetInfo(path, &info)) {
for (;;) { ESP_LOGI(kTag, "added as '%s'", info.title.c_str());
res = f_readdir(&dir, &fno); db_->Put(opt, "title:" + info.title, path);
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); });
db_->Put(opt, "title:coolkeywithoutval", leveldb::Slice());
} }
return res; 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::Initialise() -> void {
// TODO(jacqueline): Abstractions lol
scan_files("/");
} }
auto Database::Update() -> void { auto Iterator::Next() -> std::optional<std::string> {
// TODO(jacqueline): Incremental updates! if (!it_->Valid()) {
return {};
}
std::string ret = it_->key().ToString();
it_->Next();
return ret;
} }
} // namespace database } // namespace database

@ -465,7 +465,7 @@ void EspEnv::Schedule(
if (!started_background_thread_) { if (!started_background_thread_) {
started_background_thread_ = true; started_background_thread_ = true;
xTaskCreate(reinterpret_cast<TaskFunction_t>(BackgroundThreadEntryPoint), xTaskCreate(reinterpret_cast<TaskFunction_t>(BackgroundThreadEntryPoint),
"LVL_ONEOFF", 2048, reinterpret_cast<void*>(this), 3, NULL); "LVL_ONEOFF", 16 * 1024, reinterpret_cast<void*>(this), 3, NULL);
} }
BackgroundWorkItem item(background_work_function, background_work_arg); BackgroundWorkItem item(background_work_function, background_work_arg);

@ -1,13 +1,18 @@
#pragma once #pragma once
#include <string>
#include <memory> #include <memory>
#include <optional>
#include "leveldb/cache.h" #include "leveldb/cache.h"
#include "leveldb/db.h" #include "leveldb/db.h"
#include "leveldb/iterator.h"
#include "result.hpp" #include "result.hpp"
namespace database { namespace database {
class Iterator;
class Database { class Database {
public: public:
enum DatabaseError { enum DatabaseError {
@ -18,7 +23,10 @@ class Database {
~Database(); ~Database();
auto Initialise() -> void; auto Initialise() -> void;
auto Update() -> void; auto ByTitle() -> Iterator;
Database(const Database&) = delete;
Database& operator=(const Database&) = delete;
private: private:
std::unique_ptr<leveldb::DB> db_; std::unique_ptr<leveldb::DB> db_;
@ -27,4 +35,17 @@ class Database {
Database(leveldb::DB* db, leveldb::Cache* cache); Database(leveldb::DB* db, leveldb::Cache* cache);
}; };
class Iterator {
public:
explicit Iterator(leveldb::Iterator *it) : it_(it) {}
auto Next() -> std::optional<std::string>;
Iterator(const Iterator&) = delete;
Iterator& operator=(const Iterator&) = delete;
private:
std::unique_ptr<leveldb::Iterator> it_;
};
} // namespace database } // namespace database

@ -12,7 +12,7 @@ namespace database {
static_assert(sizeof(TCHAR) == sizeof(char), "TCHAR must be CHAR"); static_assert(sizeof(TCHAR) == sizeof(char), "TCHAR must be CHAR");
template <typename Callback> template <typename Callback>
auto FindFiles(FATFS* fs, const std::string& root, Callback cb) -> void { auto FindFiles(const std::string& root, Callback cb) -> void {
std::deque<std::string> to_explore; std::deque<std::string> to_explore;
to_explore.push_back(root); 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(); std::string next_path_str = to_explore.front();
const TCHAR* next_path = static_cast<const TCHAR*>(next_path_str.c_str()); const TCHAR* next_path = static_cast<const TCHAR*>(next_path_str.c_str());
DIR dir; FF_DIR dir;
FRESULT res = f_opendir(&dir, next_path); FRESULT res = f_opendir(&dir, next_path);
if (res != FR_OK) { if (res != FR_OK) {
// TODO: log. // TODO: log.
@ -30,10 +30,10 @@ auto FindFiles(FATFS* fs, const std::string& root, Callback cb) -> void {
for (;;) { for (;;) {
FILINFO info; FILINFO info;
res = f_readdir(&dir, &info); res = f_readdir(&dir, &info);
if (info.fname == NULL) { if (res != FR_OK || info.fname[0] == 0) {
// No more files in the directory. // No more files in the directory.
break; 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. // System or hidden file. Ignore it and move on.
continue; continue;
} else { } else {
@ -45,7 +45,8 @@ auto FindFiles(FATFS* fs, const std::string& root, Callback cb) -> void {
to_explore.push_back(full_path.str()); to_explore.push_back(full_path.str());
} else { } else {
// This is a file! Let the callback know about it. // 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());
} }
} }
} }

@ -1,3 +1,14 @@
#pragma once #pragma once
namespace database {} // namespace database #include <string>
namespace database {
struct FileInfo {
bool is_playable;
std::string title;
};
auto GetInfo(const std::string &path, FileInfo *out) -> bool;
} // namespace database

@ -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

@ -9,7 +9,9 @@
#include <string> #include <string>
#include "audio_playback.hpp" #include "audio_playback.hpp"
#include "database.hpp"
#include "esp_console.h" #include "esp_console.h"
#include "esp_log.h"
namespace console { namespace console {
@ -145,7 +147,55 @@ void RegisterAudioStatus() {
esp_console_cmd_register(&cmd); 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<std::string> 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; sInstance = this;
} }
AppConsole::~AppConsole() { AppConsole::~AppConsole() {
@ -158,6 +208,8 @@ auto AppConsole::RegisterExtraComponents() -> void {
RegisterToggle(); RegisterToggle();
RegisterVolume(); RegisterVolume();
RegisterAudioStatus(); RegisterAudioStatus();
RegisterDbInit();
RegisterDbTitles();
} }
} // namespace console } // namespace console

@ -4,16 +4,18 @@
#include "audio_playback.hpp" #include "audio_playback.hpp"
#include "console.hpp" #include "console.hpp"
#include "database.hpp"
#include "storage.hpp" #include "storage.hpp"
namespace console { namespace console {
class AppConsole : public Console { class AppConsole : public Console {
public: public:
explicit AppConsole(audio::AudioPlayback* playback); explicit AppConsole(audio::AudioPlayback* playback, database::Database *database);
virtual ~AppConsole(); virtual ~AppConsole();
audio::AudioPlayback* playback_; audio::AudioPlayback* playback_;
database::Database *database_;
protected: protected:
virtual auto RegisterExtraComponents() -> void; virtual auto RegisterExtraComponents() -> void;

@ -38,6 +38,7 @@
static const char* TAG = "MAIN"; static const char* TAG = "MAIN";
void db_main(void* whatever) { void db_main(void* whatever) {
database::Database **arg_db = reinterpret_cast<database::Database**>(whatever);
ESP_LOGI(TAG, "Init database"); ESP_LOGI(TAG, "Init database");
std::unique_ptr<database::Database> db; std::unique_ptr<database::Database> db;
auto db_res = database::Database::Open(); auto db_res = database::Database::Open();
@ -48,13 +49,13 @@ void db_main(void* whatever) {
ESP_LOGI(TAG, "database good :)"); ESP_LOGI(TAG, "database good :)");
} }
vTaskDelay(pdMS_TO_TICKS(2000)); *arg_db = db.get();
db->Initialise(); db->ByTitle();
vTaskDelay(pdMS_TO_TICKS(2000)); while (1) {
vTaskDelay(portMAX_DELAY);
db.reset(); }
vTaskDelete(NULL); vTaskDelete(NULL);
} }
@ -89,7 +90,8 @@ extern "C" void app_main(void) {
StaticTask_t database_task_buffer = {}; StaticTask_t database_task_buffer = {};
StackType_t* database_stack = reinterpret_cast<StackType_t*>( StackType_t* database_stack = reinterpret_cast<StackType_t*>(
heap_caps_malloc(db_stack_size, MALLOC_CAP_SPIRAM)); 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); &database_task_buffer);
ESP_LOGI(TAG, "Init touch wheel"); ESP_LOGI(TAG, "Init touch wheel");
@ -110,7 +112,7 @@ extern "C" void app_main(void) {
vTaskDelay(pdMS_TO_TICKS(1000)); vTaskDelay(pdMS_TO_TICKS(1000));
ESP_LOGI(TAG, "Launch console"); ESP_LOGI(TAG, "Launch console");
console::AppConsole console(playback.get()); console::AppConsole console(playback.get(), db);
console.Launch(); console.Launch();
uint8_t prev_position = 0; uint8_t prev_position = 0;

Loading…
Cancel
Save