1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
|
/*
* Copyright 2023 jacqueline <me@jacqueline.id.au>
*
* SPDX-License-Identifier: GPL-3.0-only
*/
#include "index.hpp"
#include <cstdint>
#include <sstream>
#include <string>
#include <variant>
#include "collation.hpp"
#include "esp_log.h"
#include "komihash.h"
#include "leveldb/write_batch.h"
#include "records.hpp"
namespace database {
const IndexInfo kAlbumsByArtist{
.id = 1,
.name = "Albums by Artist",
.components = {Tag::kArtist, Tag::kAlbum, Tag::kAlbumTrack},
};
const IndexInfo kTracksByGenre{
.id = 2,
.name = "Tracks by Genre",
.components = {Tag::kGenre, 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::kAlbumTrack},
};
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 "0";
case Tag::kDuration:
default:
return {};
}
}
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,
},
.item = {},
.track = {},
};
for (std::uint8_t i = 0; i < info.components.size(); i++) {
Tag component = info.components.at(i);
// Fill in the text for this depth.
std::pmr::string value;
if (component == Tag::kAlbumTrack) {
// Track numbers are a special case, since they're numbers rather than
// text.
auto pmr_num = t.tags().at(component).value_or("0");
// std::pmr continues to be a true disappointment.
std::string raw_num{pmr_num.data(), pmr_num.size()};
uint32_t num = std::stoi(raw_num);
key.item = std::pmr::string{reinterpret_cast<char*>(&num), 4};
} else {
auto text = t.tags().at(component);
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();
}
out.push_back(std::make_pair(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);
}
}
return out;
}
auto ExpandHeader(const IndexKey::Header& header,
const std::optional<std::pmr::string>& 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
|