Add basic track queue save/load support

Not wired up yet; I need to do a bunch of cleanup before i wire it in
custom
jacqueline 1 year ago
parent 4f5422e906
commit 009f69c929
  1. 3
      src/audio/include/track_queue.hpp
  2. 181
      src/audio/track_queue.cpp
  3. 117
      src/database/database.cpp
  4. 20
      src/database/include/database.hpp

@ -86,6 +86,9 @@ class TrackQueue {
*/
auto Clear(Editor&) -> void;
auto Save(std::weak_ptr<database::Database>) -> void;
auto Load(std::weak_ptr<database::Database>) -> void;
// Cannot be copied or moved.
TrackQueue(const TrackQueue&) = delete;
TrackQueue& operator=(const TrackQueue&) = delete;

@ -5,6 +5,7 @@
*/
#include "track_queue.hpp"
#include <stdint.h>
#include <algorithm>
#include <mutex>
@ -13,6 +14,8 @@
#include "audio_events.hpp"
#include "audio_fsm.hpp"
#include "cppbor.h"
#include "cppbor_parse.h"
#include "database.hpp"
#include "event_queue.hpp"
#include "memory_resource.hpp"
@ -24,6 +27,11 @@ namespace audio {
[[maybe_unused]] static constexpr char kTag[] = "tracks";
static const std::string kSerialiseKey = "queue";
static const std::string kCurrentKey = "cur";
static const std::string kPlayedKey = "prev";
static const std::string kEnqueuedKey = "next";
TrackQueue::Editor::Editor(TrackQueue& queue)
: lock_(queue.mutex_), has_current_changed_(false) {}
@ -227,4 +235,177 @@ auto TrackQueue::Clear(Editor& ed) -> void {
enqueued_.clear();
}
auto TrackQueue::Save(std::weak_ptr<database::Database> db) -> void {
cppbor::Map root{};
if (current_) {
root.add(cppbor::Bstr{kCurrentKey}, cppbor::Uint{*current_});
}
cppbor::Array played{};
for (const auto& id : played_) {
played.add(cppbor::Uint{id});
}
root.add(cppbor::Bstr{kPlayedKey}, std::move(played));
cppbor::Array enqueued{};
for (const auto& item : enqueued_) {
std::visit(
[&](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, database::TrackId>) {
enqueued.add(cppbor::Uint{arg});
} else if constexpr (std::is_same_v<T, database::TrackIterator>) {
enqueued.add(arg.cbor());
}
},
item);
}
root.add(cppbor::Bstr{kEnqueuedKey}, std::move(enqueued));
auto db_lock = db.lock();
if (!db_lock) {
return;
}
db_lock->Put(kSerialiseKey, root.toString());
}
class Parser : public cppbor::ParseClient {
public:
Parser(std::weak_ptr<database::Database> db,
std::optional<database::TrackId>& current,
std::pmr::vector<database::TrackId>& played,
std::pmr::vector<TrackQueue::Item>& enqueued)
: state_(State::kInit),
db_(db),
current_(current),
played_(played),
enqueued_(enqueued) {}
virtual ParseClient* item(std::unique_ptr<cppbor::Item>& item,
const uint8_t* hdrBegin,
const uint8_t* valueBegin,
const uint8_t* end) override {
switch (state_) {
case State::kInit:
if (item->type() == cppbor::MAP) {
state_ = State::kRoot;
}
break;
case State::kRoot:
if (item->type() != cppbor::TSTR) {
break;
}
if (item->asTstr()->value() == kCurrentKey) {
state_ = State::kCurrent;
} else if (item->asTstr()->value() == kPlayedKey) {
state_ = State::kPlayed;
} else if (item->asTstr()->value() == kEnqueuedKey) {
state_ = State::kEnqueued;
}
break;
case State::kCurrent:
if (item->type() == cppbor::UINT) {
current_ = item->asUint()->value();
}
state_ = State::kRoot;
break;
case State::kPlayed:
if (item->type() == cppbor::UINT) {
played_.push_back(item->asUint()->value());
}
break;
case State::kEnqueued:
if (item->type() == cppbor::UINT) {
played_.push_back(item->asUint()->value());
} else if (item->type() == cppbor::ARRAY) {
queue_depth_ = 1;
state_ = State::kEnqueuedIterator;
}
break;
case State::kEnqueuedIterator:
if (item->type() == cppbor::MAP || item->type() == cppbor::ARRAY) {
queue_depth_++;
}
break;
case State::kFinished:
break;
}
return this;
}
ParseClient* itemEnd(std::unique_ptr<cppbor::Item>& item,
const uint8_t* hdrBegin,
const uint8_t* valueBegin,
const uint8_t* end) override {
std::optional<database::TrackIterator> parsed_it;
switch (state_) {
case State::kInit:
case State::kRoot:
case State::kCurrent:
state_ = State::kFinished;
break;
case State::kEnqueued:
case State::kPlayed:
state_ = State::kRoot;
break;
case State::kEnqueuedIterator:
if (item->type() == cppbor::MAP || item->type() == cppbor::ARRAY) {
queue_depth_++;
}
if (queue_depth_ == 0) {
parsed_it = database::TrackIterator::Parse(db_, *item->asArray());
if (parsed_it) {
enqueued_.push_back(std::move(*parsed_it));
}
}
state_ = State::kEnqueued;
break;
case State::kFinished:
break;
}
return this;
}
void error(const uint8_t* position,
const std::string& errorMessage) override {
ESP_LOGE(kTag, "restoring saved queue failed: %s", errorMessage.c_str());
}
private:
enum class State {
kInit,
kRoot,
kCurrent,
kPlayed,
kEnqueued,
kEnqueuedIterator,
kFinished,
} state_;
std::weak_ptr<database::Database> db_;
int queue_depth_;
std::optional<database::TrackId>& current_;
std::pmr::vector<database::TrackId>& played_;
std::pmr::vector<TrackQueue::Item>& enqueued_;
};
auto TrackQueue::Load(std::weak_ptr<database::Database> db) -> void {
auto db_lock = db.lock();
if (!db_lock) {
return;
}
auto raw = db_lock->Get(kSerialiseKey);
if (!raw) {
return;
}
Parser p{db, current_, played_, enqueued_};
const uint8_t* data = reinterpret_cast<const uint8_t*>(raw->data());
cppbor::parse(data, data + raw->size(), &p);
}
} // namespace audio

@ -18,6 +18,7 @@
#include <sstream>
#include "collation.hpp"
#include "cppbor.h"
#include "esp_log.h"
#include "ff.h"
#include "freertos/projdefs.h"
@ -52,6 +53,8 @@ static const char kDbPath[] = "/.tangara-db";
static const char kKeyDbVersion[] = "schema_version";
static const uint8_t kCurrentDbVersion = 3;
static const char kKeyCustom[] = "U\0";
static const char kKeyCollator[] = "collator";
static const char kKeyTrackId[] = "next_track_id";
@ -197,6 +200,19 @@ Database::~Database() {
sIsDbOpen.store(false);
}
auto Database::Put(const std::string& key, const std::string& val) -> void {
db_->Put(leveldb::WriteOptions{}, kKeyCustom + key, val);
}
auto Database::Get(const std::string& key) -> std::optional<std::string> {
std::string val;
auto res = db_->Get(leveldb::ReadOptions{}, kKeyCustom + key, &val);
if (!res.ok()) {
return {};
}
return val;
}
auto Database::Update() -> std::future<void> {
events::Ui().Dispatch(event::UpdateStarted{});
return worker_task_->Dispatch<void>([&]() -> void {
@ -883,6 +899,45 @@ Iterator::Iterator(std::weak_ptr<Database> db, const IndexInfo& idx)
.page_size = 1};
}
auto Iterator::Parse(std::weak_ptr<Database> db, const cppbor::Array& encoded)
-> std::optional<Iterator> {
// Ensure the input looks reasonable.
if (encoded.size() != 3) {
return {};
}
if (encoded[0]->type() != cppbor::TSTR) {
return {};
}
const std::string& prefix = encoded[0]->asTstr()->value();
std::optional<Continuation> current_pos{};
if (encoded[1]->type() == cppbor::TSTR) {
const std::string& key = encoded[1]->asTstr()->value();
current_pos = Continuation{
.prefix = {prefix.data(), prefix.size()},
.start_key = {key.data(), key.size()},
.forward = true,
.was_prev_forward = true,
.page_size = 1,
};
}
std::optional<Continuation> prev_pos{};
if (encoded[2]->type() == cppbor::TSTR) {
const std::string& key = encoded[2]->asTstr()->value();
current_pos = Continuation{
.prefix = {prefix.data(), prefix.size()},
.start_key = {key.data(), key.size()},
.forward = false,
.was_prev_forward = false,
.page_size = 1,
};
}
return Iterator{db, std::move(current_pos), std::move(prev_pos)};
}
Iterator::Iterator(std::weak_ptr<Database> db, const Continuation& c)
: db_(db), pos_mutex_(), current_pos_(c), prev_pos_() {}
@ -892,6 +947,11 @@ Iterator::Iterator(const Iterator& other)
current_pos_(other.current_pos_),
prev_pos_(other.prev_pos_) {}
Iterator::Iterator(std::weak_ptr<Database> db,
std::optional<Continuation>&& cur,
std::optional<Continuation>&& prev)
: db_(db), current_pos_(cur), prev_pos_(prev) {}
Iterator& Iterator::operator=(const Iterator& other) {
current_pos_ = other.current_pos_;
prev_pos_ = other.prev_pos_;
@ -995,6 +1055,53 @@ auto Iterator::InvokeNull(Callback cb) -> void {
std::invoke(cb, std::optional<IndexRecord>{});
}
auto Iterator::cbor() const -> cppbor::Array&& {
cppbor::Array res;
std::pmr::string prefix;
if (current_pos_) {
prefix = current_pos_->prefix;
} else if (prev_pos_) {
prefix = prev_pos_->prefix;
} else {
ESP_LOGW(kTag, "iterator has no prefix");
return std::move(res);
}
if (current_pos_) {
res.add(cppbor::Tstr(current_pos_->start_key));
} else {
res.add(cppbor::Null());
}
if (prev_pos_) {
res.add(cppbor::Tstr(prev_pos_->start_key));
} else {
res.add(cppbor::Null());
}
return std::move(res);
}
auto TrackIterator::Parse(std::weak_ptr<Database> db,
const cppbor::Array& encoded)
-> std::optional<TrackIterator> {
TrackIterator ret{db};
for (const auto& item : encoded) {
if (item->type() == cppbor::ARRAY) {
auto it = Iterator::Parse(db, *item->asArray());
if (it) {
ret.levels_.push_back(std::move(*it));
} else {
return {};
}
}
}
return ret;
}
TrackIterator::TrackIterator(const Iterator& it) : db_(it.db_), levels_() {
if (it.current_pos_) {
levels_.push_back(it);
@ -1005,6 +1112,8 @@ TrackIterator::TrackIterator(const Iterator& it) : db_(it.db_), levels_() {
TrackIterator::TrackIterator(const TrackIterator& other)
: db_(other.db_), levels_(other.levels_) {}
TrackIterator::TrackIterator(std::weak_ptr<Database> db) : db_(db), levels_() {}
TrackIterator& TrackIterator::operator=(TrackIterator&& other) {
levels_ = std::move(other.levels_);
return *this;
@ -1057,4 +1166,12 @@ auto TrackIterator::NextLeaf() -> void {
}
}
auto TrackIterator::cbor() const -> cppbor::Array&& {
cppbor::Array res;
for (const auto& i : levels_) {
res.add(i.cbor());
}
return std::move(res);
}
} // namespace database

@ -18,6 +18,7 @@
#include <vector>
#include "collation.hpp"
#include "cppbor.h"
#include "file_gatherer.hpp"
#include "index.hpp"
#include "leveldb/cache.h"
@ -105,6 +106,9 @@ class Database {
~Database();
auto Put(const std::string& key, const std::string& val) -> void;
auto Get(const std::string& key) -> std::optional<std::string>;
auto Update() -> std::future<void>;
auto GetTrackPath(TrackId id) -> std::future<std::optional<std::pmr::string>>;
@ -194,6 +198,9 @@ auto Database::ParseRecord<std::pmr::string>(const leveldb::Slice& key,
*/
class Iterator {
public:
static auto Parse(std::weak_ptr<Database>, const cppbor::Array&)
-> std::optional<Iterator>;
Iterator(std::weak_ptr<Database>, const IndexInfo&);
Iterator(std::weak_ptr<Database>, const Continuation&);
Iterator(const Iterator&);
@ -213,7 +220,13 @@ class Iterator {
auto Size() const -> size_t;
auto cbor() const -> cppbor::Array&&;
private:
Iterator(std::weak_ptr<Database>,
std::optional<Continuation>&&,
std::optional<Continuation>&&);
friend class TrackIterator;
auto InvokeNull(Callback) -> void;
@ -227,6 +240,9 @@ class Iterator {
class TrackIterator {
public:
static auto Parse(std::weak_ptr<Database>, const cppbor::Array&)
-> std::optional<TrackIterator>;
TrackIterator(const Iterator&);
TrackIterator(const TrackIterator&);
@ -235,7 +251,11 @@ class TrackIterator {
auto Next() -> std::optional<TrackId>;
auto Size() const -> size_t;
auto cbor() const -> cppbor::Array&&;
private:
TrackIterator(std::weak_ptr<Database>);
auto NextLeaf() -> void;
std::weak_ptr<Database> db_;

Loading…
Cancel
Save