Add lua functions to get database content

custom
jacqueline 1 year ago
parent f34b640588
commit d70ec9bf44
  1. 6
      lua/main_menu.lua
  2. 44
      src/database/database.cpp
  3. 17
      src/database/include/database.hpp
  4. 2
      src/lua/CMakeLists.txt
  5. 37
      src/lua/bridge.cpp
  6. 15
      src/lua/include/lua_database.hpp
  7. 15
      src/lua/include/lua_queue.hpp
  8. 199
      src/lua/lua_database.cpp
  9. 46
      src/lua/lua_queue.cpp
  10. 59
      src/lua/stubs/database.lua

@ -30,9 +30,9 @@ return function()
legacy_ui.open_now_playing(); legacy_ui.open_now_playing();
end) end)
local indexes = database.get_indexes() local indexes = database.indexes()
for id, name in ipairs(indexes) do for id, idx in ipairs(indexes) do
local btn = menu.list:add_btn(nil, name) local btn = menu.list:add_btn(nil, tostring(idx))
btn:onClicked(function() btn:onClicked(function()
legacy_ui.open_browse(id); legacy_ui.open_browse(id);
end) end)

@ -857,4 +857,48 @@ auto IndexRecord::Expand(std::size_t page_size) const
}; };
} }
Iterator::Iterator(std::weak_ptr<Database> db, const IndexInfo& idx)
: db_(db), prev_pos_(), current_pos_() {
std::string prefix = EncodeIndexPrefix(
IndexKey::Header{.id = idx.id, .depth = 0, .components_hash = 0});
current_pos_ = Continuation{.prefix = {prefix.data(), prefix.size()},
.start_key = {prefix.data(), prefix.size()},
.forward = true,
.was_prev_forward = true,
.page_size = 1};
}
Iterator::Iterator(std::weak_ptr<Database> db, const Continuation& c)
: db_(db), prev_pos_(), current_pos_(c) {}
auto Iterator::Prev() -> std::optional<IndexRecord> {
if (!prev_pos_) {
return {};
}
auto db = db_.lock();
if (!db) {
return {};
}
std::unique_ptr<Result<IndexRecord>> res{
db->GetPage<IndexRecord>(&*prev_pos_).get()};
prev_pos_ = res->prev_page();
current_pos_ = prev_pos_;
return *res->values()[0];
}
auto Iterator::Next() -> std::optional<IndexRecord> {
if (!current_pos_) {
return {};
}
auto db = db_.lock();
if (!db) {
return {};
}
std::unique_ptr<Result<IndexRecord>> res{
db->GetPage<IndexRecord>(&*current_pos_).get()};
prev_pos_ = current_pos_;
current_pos_ = res->next_page();
return *res->values()[0];
}
} // namespace database } // namespace database

@ -183,4 +183,21 @@ auto Database::ParseRecord<std::pmr::string>(const leveldb::Slice& key,
const leveldb::Slice& val) const leveldb::Slice& val)
-> std::shared_ptr<std::pmr::string>; -> std::shared_ptr<std::pmr::string>;
/*
* Utility for accessing a large set of database records, one record at a time.
*/
class Iterator {
public:
Iterator(std::weak_ptr<Database>, const IndexInfo&);
Iterator(std::weak_ptr<Database>, const Continuation&);
auto Prev() -> std::optional<IndexRecord>;
auto Next() -> std::optional<IndexRecord>;
private:
std::weak_ptr<Database> db_;
std::optional<Continuation> prev_pos_;
std::optional<Continuation> current_pos_;
};
} // namespace database } // namespace database

@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-only # SPDX-License-Identifier: GPL-3.0-only
idf_component_register( idf_component_register(
SRCS "lua_thread.cpp" "bridge.cpp" "property.cpp" SRCS "lua_thread.cpp" "bridge.cpp" "property.cpp" "lua_database.cpp" "lua_queue.cpp"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database" "esp_timer" "battery" "esp-idf-lua" "luavgl") REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database" "esp_timer" "battery" "esp-idf-lua" "luavgl")
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})

@ -5,13 +5,19 @@
*/ */
#include "bridge.hpp" #include "bridge.hpp"
#include <sys/_stdint.h>
#include <memory> #include <memory>
#include <string> #include <string>
#include "database.hpp"
#include "esp_log.h" #include "esp_log.h"
#include "index.hpp"
#include "lauxlib.h" #include "lauxlib.h"
#include "lua.h"
#include "lua.hpp" #include "lua.hpp"
#include "lua_database.hpp"
#include "lua_queue.hpp"
#include "lvgl.h" #include "lvgl.h"
#include "event_queue.hpp" #include "event_queue.hpp"
@ -22,6 +28,7 @@
namespace lua { namespace lua {
[[maybe_unused]] static constexpr char kTag[] = "lua_bridge"; [[maybe_unused]] static constexpr char kTag[] = "lua_bridge";
static constexpr char kBridgeKey[] = "bridge"; static constexpr char kBridgeKey[] = "bridge";
static auto open_settings_fn(lua_State* state) -> int { static auto open_settings_fn(lua_State* state) -> int {
@ -54,32 +61,6 @@ static auto lua_legacy_ui(lua_State* state) -> int {
return 1; return 1;
} }
static auto get_indexes(lua_State* state) -> int {
Bridge* instance = Bridge::Get(state);
lua_newtable(state);
auto db = instance->services().database().lock();
if (!db) {
return 1;
}
for (const auto& i : db->GetIndexes()) {
lua_pushstring(state, i.name.c_str());
lua_rawseti(state, -2, i.id);
}
return 1;
}
static const struct luaL_Reg kDatabaseFuncs[] = {{"get_indexes", get_indexes},
{NULL, NULL}};
static auto lua_database(lua_State* state) -> int {
luaL_newlib(state, kDatabaseFuncs);
return 1;
}
auto Bridge::Get(lua_State* state) -> Bridge* { auto Bridge::Get(lua_State* state) -> Bridge* {
lua_pushstring(state, kBridgeKey); lua_pushstring(state, kBridgeKey);
lua_gettable(state, LUA_REGISTRYINDEX); lua_gettable(state, LUA_REGISTRYINDEX);
@ -95,8 +76,8 @@ Bridge::Bridge(system_fsm::ServiceLocator& services, lua_State& s)
luaL_requiref(&s, "legacy_ui", lua_legacy_ui, true); luaL_requiref(&s, "legacy_ui", lua_legacy_ui, true);
lua_pop(&s, 1); lua_pop(&s, 1);
luaL_requiref(&s, "database", lua_database, true); RegisterDatabaseModule(&s);
lua_pop(&s, 1); RegisterQueueModule(&s);
} }
static auto new_property_module(lua_State* state) -> int { static auto new_property_module(lua_State* state) -> int {

@ -0,0 +1,15 @@
/*
* Copyright 2023 jacqueline <me@jacqueline.id.au>
*
* SPDX-License-Identifier: GPL-3.0-only
*/
#pragma once
#include "lua.hpp"
namespace lua {
auto RegisterDatabaseModule(lua_State*) -> void;
} // namespace lua

@ -0,0 +1,15 @@
/*
* Copyright 2023 jacqueline <me@jacqueline.id.au>
*
* SPDX-License-Identifier: GPL-3.0-only
*/
#pragma once
#include "lua.hpp"
namespace lua {
auto RegisterQueueModule(lua_State*) -> void;
} // namespace lua

@ -0,0 +1,199 @@
/*
* Copyright 2023 jacqueline <me@jacqueline.id.au>
*
* SPDX-License-Identifier: GPL-3.0-only
*/
#include "lua_database.hpp"
#include <memory>
#include <string>
#include "lua.hpp"
#include "esp_log.h"
#include "lauxlib.h"
#include "lua.h"
#include "lvgl.h"
#include "database.hpp"
#include "event_queue.hpp"
#include "index.hpp"
#include "property.hpp"
#include "service_locator.hpp"
#include "ui_events.hpp"
namespace lua {
[[maybe_unused]] static constexpr char kTag[] = "lua_db";
static constexpr char kDbIndexMetatable[] = "db_index";
static constexpr char kDbRecordMetatable[] = "db_record";
static constexpr char kDbIteratorMetatable[] = "db_record";
static auto indexes(lua_State* state) -> int {
Bridge* instance = Bridge::Get(state);
lua_newtable(state);
auto db = instance->services().database().lock();
if (!db) {
return 1;
}
for (const auto& i : db->GetIndexes()) {
database::IndexInfo** data = reinterpret_cast<database::IndexInfo**>(
lua_newuserdata(state, sizeof(uintptr_t)));
luaL_setmetatable(state, kDbIndexMetatable);
*data = new database::IndexInfo{i};
lua_rawseti(state, -2, i.id);
}
return 1;
}
static const struct luaL_Reg kDatabaseFuncs[] = {{"indexes", indexes},
{NULL, NULL}};
static auto db_iterate(lua_State* state) -> int {
database::Iterator* it = *reinterpret_cast<database::Iterator**>(
lua_touserdata(state, lua_upvalueindex(1)));
auto res = it->Next();
if (res) {
database::IndexRecord** record = reinterpret_cast<database::IndexRecord**>(
lua_newuserdata(state, sizeof(uintptr_t)));
*record = new database::IndexRecord(*res);
luaL_setmetatable(state, kDbRecordMetatable);
return 1;
}
return 0;
}
static auto db_iterator_gc(lua_State* state) -> int {
database::Iterator** it = reinterpret_cast<database::Iterator**>(
luaL_checkudata(state, 1, kDbIteratorMetatable));
if (it != NULL) {
delete *it;
}
return 0;
}
static auto push_iterator(
lua_State* state,
std::variant<database::Continuation, database::IndexInfo> val) -> void {
Bridge* instance = Bridge::Get(state);
database::Iterator** data = reinterpret_cast<database::Iterator**>(
lua_newuserdata(state, sizeof(uintptr_t)));
std::visit(
[&](auto&& arg) {
*data = new database::Iterator(instance->services().database(), arg);
},
val);
luaL_setmetatable(state, kDbIteratorMetatable);
lua_pushcclosure(state, db_iterate, 1);
}
static auto record_text(lua_State* state) -> int {
database::IndexRecord* data = *reinterpret_cast<database::IndexRecord**>(
luaL_checkudata(state, 1, kDbRecordMetatable));
lua_pushstring(state,
data->text().value_or("[tell jacqueline u saw this]").c_str());
return 1;
}
static auto record_contents(lua_State* state) -> int {
database::IndexRecord* data = *reinterpret_cast<database::IndexRecord**>(
luaL_checkudata(state, 1, kDbRecordMetatable));
if (data->track()) {
lua_pushinteger(state, *data->track());
} else {
push_iterator(state, data->Expand(1).value());
}
return 1;
}
static auto record_gc(lua_State* state) -> int {
database::IndexRecord** data = reinterpret_cast<database::IndexRecord**>(
luaL_checkudata(state, 1, kDbRecordMetatable));
if (data != NULL) {
delete *data;
}
return 0;
}
static const struct luaL_Reg kDbRecordFuncs[] = {{"title", record_text},
{"contents", record_contents},
{"__tostring", record_text},
{"__gc", record_gc},
{NULL, NULL}};
static auto index_name(lua_State* state) -> int {
database::IndexInfo** data = reinterpret_cast<database::IndexInfo**>(
luaL_checkudata(state, 1, kDbIndexMetatable));
if (data == NULL) {
return 0;
}
lua_pushstring(state, (*data)->name.c_str());
return 1;
}
static auto index_iter(lua_State* state) -> int {
database::IndexInfo** data = reinterpret_cast<database::IndexInfo**>(
luaL_checkudata(state, 1, kDbIndexMetatable));
if (data == NULL) {
return 0;
}
push_iterator(state, **data);
return 1;
}
static auto index_gc(lua_State* state) -> int {
database::IndexInfo** data = reinterpret_cast<database::IndexInfo**>(
luaL_checkudata(state, 1, kDbIndexMetatable));
if (data != NULL) {
delete *data;
}
return 0;
}
static const struct luaL_Reg kDbIndexFuncs[] = {{"name", index_name},
{"iter", index_iter},
{"__tostring", index_name},
{"__gc", index_gc},
{NULL, NULL}};
static auto lua_database(lua_State* state) -> int {
// Metatable for indexes
luaL_newmetatable(state, kDbIndexMetatable);
lua_pushliteral(state, "__index");
lua_pushvalue(state, -2);
lua_settable(state, -3); // metatable.__index = metatable
// Add member funcs to the metatable.
luaL_setfuncs(state, kDbIndexFuncs, 0);
luaL_newmetatable(state, kDbIteratorMetatable);
lua_pushliteral(state, "__gc");
lua_pushcfunction(state, db_iterator_gc);
lua_settable(state, -3);
luaL_newmetatable(state, kDbRecordMetatable);
lua_pushliteral(state, "__index");
lua_pushvalue(state, -2);
lua_settable(state, -3); // metatable.__index = metatable
luaL_setfuncs(state, kDbRecordFuncs, 0);
luaL_newlib(state, kDatabaseFuncs);
return 1;
}
auto RegisterDatabaseModule(lua_State* s) -> void {
luaL_requiref(s, "database", lua_database, true);
lua_pop(s, 1);
}
} // namespace lua

@ -0,0 +1,46 @@
/*
* Copyright 2023 jacqueline <me@jacqueline.id.au>
*
* SPDX-License-Identifier: GPL-3.0-only
*/
#include "lua_database.hpp"
#include <memory>
#include <string>
#include "lua.hpp"
#include "esp_log.h"
#include "lauxlib.h"
#include "lua.h"
#include "lvgl.h"
#include "database.hpp"
#include "event_queue.hpp"
#include "index.hpp"
#include "property.hpp"
#include "service_locator.hpp"
#include "ui_events.hpp"
namespace lua {
[[maybe_unused]] static constexpr char kTag[] = "lua_queue";
static auto queue_add(lua_State* state) -> int {
return 0;
}
static const struct luaL_Reg kQueueFuncs[] = {{"add", queue_add}, {NULL, NULL}};
static auto lua_queue(lua_State* state) -> int {
luaL_newlib(state, kQueueFuncs);
return 1;
}
auto RegisterQueueModule(lua_State* s) -> void {
luaL_requiref(s, "queue", lua_queue, true);
lua_pop(s, 1);
}
} // namespace lua

@ -0,0 +1,59 @@
--- Module for accessing and updating data about the user's library of tracks.
-- @module database
local database = {}
--- Returns a list of all indexes in the database.
-- @treturn Array(Index)
function database.indexes() end
--- An iterator is a userdata type that behaves like an ordinary Lua iterator.
-- @type Iterator
local Iterator = {}
--- A TrackId is a unique identifier, representing a playable track in the
--- user's library.
-- @type TrackId
local TrackId = {}
--- A record is an item within an Index, representing some value at a specific
--- depth.
-- @type Record
local Record = {}
--- Gets the human-readable text representing this record. The `__tostring`
--- metatable function is an alias of this function.
-- @treturn string
function Record:title() end
--- Returns the value that this record represents. This may be either a track
--- id, for records which uniquely identify a track, or it may be a new
--- Iterator representing the next level of depth for the current index.
---
--- For example, each Record in the "All Albums" index corresponds to an entire
--- album of tracks; the 'contents' of such a Record is an iterator returning
--- each track in the album represented by the Record. The contents of each of
--- the returned 'track' Records would be a full Track, as there is no further
--- disambiguation needed.
-- @treturn TrackId|Iterator(Record)
function Record:contents() end
--- An index is heirarchical, sorted, view of the tracks within the database.
--- For example, the 'All Albums' index contains, first, a sorted list of every
--- album name in the library. Then, at the second level of the index, a sorted
--- list of every track within each album.
-- @type Index
local Index = {}
--- Gets the human-readable name of this index. This is typically something
--- like "All Albums", or "Albums by Artist". The `__tostring` metatable
--- function is an alias of this function.
-- @treturn string
function Index:name() end
--- Returns a new iterator that can be used to access every record within the
--- first level of this index.
-- @treturn Iterator(Record)
function Index:iter() end
return database
Loading…
Cancel
Save