summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2023-12-05 17:00:28 +1100
committerjacqueline <me@jacqueline.id.au>2023-12-05 17:00:28 +1100
commit009f69c929eb1d1b65d75b0937fbf3b8de5d9148 (patch)
treead7f9994226eb8c57ac7c060d4c4476f5c7d0c46 /src
parent4f5422e906b1d17720592d97bc0d5e82a71b1e5f (diff)
downloadtangara-fw-009f69c929eb1d1b65d75b0937fbf3b8de5d9148.tar.gz
Add basic track queue save/load support
Not wired up yet; I need to do a bunch of cleanup before i wire it in
Diffstat (limited to 'src')
-rw-r--r--src/audio/include/track_queue.hpp3
-rw-r--r--src/audio/track_queue.cpp181
-rw-r--r--src/database/database.cpp117
-rw-r--r--src/database/include/database.hpp20
4 files changed, 321 insertions, 0 deletions
diff --git a/src/audio/include/track_queue.hpp b/src/audio/include/track_queue.hpp
index 9d5ef5b2..bec887ae 100644
--- a/src/audio/include/track_queue.hpp
+++ b/src/audio/include/track_queue.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;
diff --git a/src/audio/track_queue.cpp b/src/audio/track_queue.cpp
index b3a128b2..eb761590 100644
--- a/src/audio/track_queue.cpp
+++ b/src/audio/track_queue.cpp
@@ -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
diff --git a/src/database/database.cpp b/src/database/database.cpp
index 03451c05..e646154e 100644
--- a/src/database/database.cpp
+++ b/src/database/database.cpp
@@ -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
diff --git a/src/database/include/database.hpp b/src/database/include/database.hpp
index 263153fb..327db3cb 100644
--- a/src/database/include/database.hpp
+++ b/src/database/include/database.hpp
@@ -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_;