summaryrefslogtreecommitdiff
path: root/src/database/index.cpp
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2023-12-12 12:59:38 +1100
committerjacqueline <me@jacqueline.id.au>2023-12-12 12:59:38 +1100
commit01eb8683733f39a6de984111f035bb8f71dcf8b8 (patch)
treefb682164eb781a26d1798294180632e63a724f52 /src/database/index.cpp
parentbd730c82b0423af65f7148bb4abe01e1c3430691 (diff)
downloadtangara-fw-01eb8683733f39a6de984111f035bb8f71dcf8b8.tar.gz
Support more datatypes in track tags
Diffstat (limited to 'src/database/index.cpp')
-rw-r--r--src/database/index.cpp190
1 files changed, 136 insertions, 54 deletions
diff --git a/src/database/index.cpp b/src/database/index.cpp
index 7d556192..857fbcc5 100644
--- a/src/database/index.cpp
+++ b/src/database/index.cpp
@@ -5,30 +5,39 @@
*/
#include "index.hpp"
+#include <sys/_stdint.h>
#include <cstdint>
+#include <iomanip>
+#include <iostream>
#include <sstream>
+#include <string>
#include <variant>
+#include <vector>
#include "collation.hpp"
+#include "cppbor.h"
#include "esp_log.h"
#include "komihash.h"
#include "leveldb/write_batch.h"
#include "records.hpp"
+#include "track.hpp"
namespace database {
+[[maybe_unused]] static const char* kTag = "index";
+
const IndexInfo kAlbumsByArtist{
.id = 1,
.name = "Albums by Artist",
- .components = {Tag::kArtist, Tag::kAlbum, Tag::kAlbumTrack},
+ .components = {Tag::kAlbumArtist, Tag::kAlbum, Tag::kAlbumOrder},
};
const IndexInfo kTracksByGenre{
.id = 2,
.name = "Tracks by Genre",
- .components = {Tag::kGenre, Tag::kTitle},
+ .components = {Tag::kGenres, Tag::kTitle},
};
const IndexInfo kAllTracks{
@@ -40,71 +49,144 @@ const IndexInfo kAllTracks{
const IndexInfo kAllAlbums{
.id = 4,
.name = "All Albums",
- .components = {Tag::kAlbum, Tag::kAlbumTrack},
+ .components = {Tag::kAlbum, Tag::kAlbumOrder},
};
-static auto missing_component_text(const Track& track, Tag tag)
- -> std::optional<std::pmr::string> {
- switch (tag) {
- case Tag::kArtist:
- return "Unknown Artist";
- case Tag::kAlbum:
- return "Unknown Album";
- case Tag::kGenre:
- return "Unknown Genre";
- case Tag::kTitle:
- return track.TitleOrFilename();
- case Tag::kAlbumTrack:
- return "0000";
- case Tag::kDuration:
- default:
- return {};
+class Indexer {
+ public:
+ Indexer(locale::ICollator& collator, const Track& t, const IndexInfo& idx)
+ : collator_(collator), track_(t), index_(idx) {}
+
+ auto index() -> std::vector<std::pair<IndexKey, std::string>>;
+
+ private:
+ auto handleLevel(const IndexKey::Header& header,
+ cpp::span<const Tag> components) -> void;
+
+ auto handleItem(const IndexKey::Header& header,
+ std::variant<std::pmr::string, uint32_t> item,
+ cpp::span<const Tag> components) -> void;
+
+ auto missing_value(Tag tag) -> TagValue {
+ switch (tag) {
+ case Tag::kTitle:
+ return track_.TitleOrFilename();
+ case Tag::kArtist:
+ return "Unknown Artist";
+ case Tag::kAlbum:
+ return "Unknown Album";
+ case Tag::kAlbumArtist:
+ return track_.tags().artist().value_or("Unknown Artist");
+ return "Unknown Album";
+ case Tag::kGenres:
+ return std::pmr::vector<std::pmr::string>{};
+ case Tag::kDisc:
+ return 0u;
+ case Tag::kTrack:
+ return 0u;
+ case Tag::kAlbumOrder:
+ return 0u;
+ }
+ return std::monostate{};
}
+
+ locale::ICollator& collator_;
+ const Track& track_;
+ const IndexInfo index_;
+
+ std::vector<std::pair<IndexKey, std::string>> out_;
+};
+
+auto Indexer::index() -> std::vector<std::pair<IndexKey, std::string>> {
+ out_.clear();
+
+ IndexKey::Header root_header{
+ .id = index_.id,
+ .depth = 0,
+ .components_hash = 0,
+ };
+ handleLevel(root_header, index_.components);
+
+ return out_;
}
-auto Index(locale::ICollator& collator, const IndexInfo& info, const Track& t)
- -> std::vector<std::pair<IndexKey, std::pmr::string>> {
- std::vector<std::pair<IndexKey, std::pmr::string>> out;
- IndexKey key{
- .header{
- .id = info.id,
- .depth = 0,
- .components_hash = 0,
+auto Indexer::handleLevel(const IndexKey::Header& header,
+ cpp::span<const Tag> components) -> void {
+ Tag component = components.front();
+ TagValue value = track_.tags().get(component);
+ if (std::holds_alternative<std::monostate>(value)) {
+ value = missing_value(component);
+ }
+
+ std::visit(
+ [&](auto&& arg) {
+ using T = std::decay_t<decltype(arg)>;
+ if constexpr (std::is_same_v<T, std::monostate>) {
+ ESP_LOGW(kTag, "dropping component without value: %s",
+ tagName(components.front()).c_str());
+ } else if constexpr (std::is_same_v<T, std::pmr::string>) {
+ handleItem(header, arg, components);
+ } else if constexpr (std::is_same_v<T, uint32_t>) {
+ handleItem(header, arg, components);
+ } else if constexpr (std::is_same_v<
+ T, cpp::span<const std::pmr::string>>) {
+ for (const auto& i : arg) {
+ handleItem(header, i, components);
+ }
+ }
},
+ value);
+}
+
+auto Indexer::handleItem(const IndexKey::Header& header,
+ std::variant<std::pmr::string, uint32_t> item,
+ cpp::span<const Tag> components) -> void {
+ IndexKey key{
+ .header = header,
.item = {},
.track = {},
};
+ std::string value;
+
+ std::string item_text;
+ std::visit(
+ [&](auto&& arg) {
+ using T = std::decay_t<decltype(arg)>;
+ if constexpr (std::is_same_v<T, std::pmr::string>) {
+ value = {arg.data(), arg.size()};
+ auto xfrm = collator_.Transform(value);
+ key.item = {xfrm.data(), xfrm.size()};
+ } else if constexpr (std::is_same_v<T, uint32_t>) {
+ value = std::to_string(arg);
+ // FIXME: this sucks lol. we should just write the number directly,
+ // LSB-first, but then we need to be able to parse it back properly.
+ std::ostringstream str;
+ str << std::setw(8) << std::setfill('0') << arg;
+ std::string encoded = str.str();
+ key.item = {encoded.data(), encoded.size()};
+ }
+ },
+ item);
- for (std::uint8_t i = 0; i < info.components.size(); i++) {
- // Fill in the text for this depth.
- auto text = t.tags().at(info.components.at(i));
- std::pmr::string value;
- if (text) {
- std::pmr::string orig = *text;
- auto xfrm = collator.Transform({orig.data(), orig.size()});
- key.item = {xfrm.data(), xfrm.size()};
- value = *text;
- } else {
- key.item = {};
- value = missing_component_text(t, info.components.at(i)).value_or("");
- }
-
- // If this is the last component, then we should also fill in the track id
- // and title.
- if (i == info.components.size() - 1) {
- key.track = t.data().id;
- value = t.TitleOrFilename();
- }
+ std::optional<IndexKey::Header> next_level;
+ if (components.size() == 1) {
+ value = track_.TitleOrFilename();
+ key.track = track_.data().id;
+ } else {
+ next_level = ExpandHeader(key.header, key.item);
+ }
- out.push_back(std::make_pair(key, value));
+ out_.emplace_back(key, value);
- // If there are more components after this, then we need to finish by
- // narrowing the header with the current title.
- if (i < info.components.size() - 1) {
- key.header = ExpandHeader(key.header, key.item);
- }
+ if (next_level) {
+ handleLevel(*next_level, components.subspan(1));
}
- return out;
+}
+
+auto Index(locale::ICollator& c, const IndexInfo& i, const Track& t)
+ -> std::vector<std::pair<IndexKey, std::string>> {
+ Indexer indexer{c, t, i};
+ return indexer.index();
}
auto ExpandHeader(const IndexKey::Header& header,