summaryrefslogtreecommitdiff
path: root/src/database/index.cpp
blob: 7d556192f92b030446d96a7b4127c60eaf63bdb3 (plain)
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
/*
 * Copyright 2023 jacqueline <me@jacqueline.id.au>
 *
 * SPDX-License-Identifier: GPL-3.0-only
 */

#include "index.hpp"

#include <cstdint>
#include <sstream>
#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 "0000";
    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++) {
    // 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();
    }

    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