From 1573a8c4cde1cd9528b422b2dcc598e37ffe94a7 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Thu, 2 May 2024 19:12:26 +1000 Subject: WIP merge cyclically dependent components into one big component --- src/tangara/database/index.cpp | 206 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 src/tangara/database/index.cpp (limited to 'src/tangara/database/index.cpp') diff --git a/src/tangara/database/index.cpp b/src/tangara/database/index.cpp new file mode 100644 index 00000000..328c3b43 --- /dev/null +++ b/src/tangara/database/index.cpp @@ -0,0 +1,206 @@ +/* + * Copyright 2023 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "index.hpp" +#include + +#include +#include +#include +#include +#include +#include +#include + +#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::kAlbumArtist, Tag::kAlbum, Tag::kAlbumOrder}, +}; + +const IndexInfo kTracksByGenre{ + .id = 2, + .name = "Tracks by Genre", + .components = {Tag::kGenres, Tag::kTitle}, +}; + +const IndexInfo kAllTracks{ + .id = 3, + .name = "All Tracks", + .components = {Tag::kTitle}, +}; + +const IndexInfo kAllAlbums{ + .id = 4, + .name = "All Albums", + .components = {Tag::kAlbum, Tag::kAlbumOrder}, +}; + +class Indexer { + public: + Indexer(locale::ICollator& collator, const Track& t, const IndexInfo& idx) + : collator_(collator), track_(t), index_(idx) {} + + auto index() -> std::vector>; + + private: + auto handleLevel(const IndexKey::Header& header, + std::span components) -> void; + + auto handleItem(const IndexKey::Header& header, + std::variant item, + std::span 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{}; + 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> out_; +}; + +auto Indexer::index() -> std::vector> { + out_.clear(); + + IndexKey::Header root_header{ + .id = index_.id, + .depth = 0, + .components_hash = 0, + }; + handleLevel(root_header, index_.components); + + return out_; +} + +auto Indexer::handleLevel(const IndexKey::Header& header, + std::span components) -> void { + Tag component = components.front(); + TagValue value = track_.tags().get(component); + if (std::holds_alternative(value)) { + value = missing_value(component); + } + + std::visit( + [&](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + ESP_LOGW(kTag, "dropping component without value: %s", + tagName(components.front()).c_str()); + } else if constexpr (std::is_same_v) { + handleItem(header, arg, components); + } else if constexpr (std::is_same_v) { + handleItem(header, arg, components); + } else if constexpr (std::is_same_v< + T, std::span>) { + for (const auto& i : arg) { + handleItem(header, i, components); + } + } + }, + value); +} + +auto Indexer::handleItem(const IndexKey::Header& header, + std::variant item, + std::span components) -> void { + IndexKey key{ + .header = header, + .item = {}, + .track = {}, + }; + std::string value; + + std::string item_text; + std::visit( + [&](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + value = {arg.data(), arg.size()}; + auto xfrm = collator_.Transform(value); + key.item = {xfrm.data(), xfrm.size()}; + } else if constexpr (std::is_same_v) { + 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); + + std::optional next_level; + if (components.size() == 1) { + value = track_.TitleOrFilename(); + key.track = track_.data().id; + } else { + next_level = ExpandHeader(key.header, key.item); + } + + out_.emplace_back(key, value); + + if (next_level) { + handleLevel(*next_level, components.subspan(1)); + } +} + +auto Index(locale::ICollator& c, const IndexInfo& i, const Track& t) + -> std::vector> { + Indexer indexer{c, t, i}; + return indexer.index(); +} + +auto ExpandHeader(const IndexKey::Header& header, + const std::optional& component) + -> IndexKey::Header { + IndexKey::Header ret{header}; + ret.depth++; + if (component) { + ret.components_hash = + komihash(component->data(), component->size(), ret.components_hash); + } else { + ret.components_hash = komihash(NULL, 0, ret.components_hash); + } + return ret; +} + +} // namespace database -- cgit v1.2.3