idk man i wrote most of this in a fugue state whilst high on the couch with my catcustom
parent
aee0474191
commit
245d9ff4b9
@ -0,0 +1,72 @@ |
|||||||
|
/*
|
||||||
|
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: GPL-3.0-only |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
|
||||||
|
#include <cstdint> |
||||||
|
#include <string> |
||||||
|
#include <variant> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
#include "leveldb/db.h" |
||||||
|
#include "leveldb/slice.h" |
||||||
|
|
||||||
|
#include "leveldb/write_batch.h" |
||||||
|
#include "shared_string.h" |
||||||
|
#include "track.hpp" |
||||||
|
|
||||||
|
namespace database { |
||||||
|
|
||||||
|
typedef uint8_t IndexId; |
||||||
|
|
||||||
|
struct IndexInfo { |
||||||
|
// Unique id for this index
|
||||||
|
IndexId id; |
||||||
|
// Localised, user-friendly description of this index. e.g. "Albums by Artist"
|
||||||
|
// or "All Tracks".
|
||||||
|
std::string name; |
||||||
|
// Specifier for how this index breaks down the database.
|
||||||
|
std::vector<Tag> components; |
||||||
|
}; |
||||||
|
|
||||||
|
struct IndexKey { |
||||||
|
struct Header { |
||||||
|
// The index that this key was created for.
|
||||||
|
IndexId id; |
||||||
|
// The number of components of IndexInfo that have already been filtered.
|
||||||
|
// For example, if an index consists of { kGenre, kArtist }, and this key
|
||||||
|
// represents an artist, then depth = 1.
|
||||||
|
std::uint8_t depth; |
||||||
|
// The cumulative hash of all filtered components, in order. For example, if
|
||||||
|
// an index consists of { kArtist, kAlbum, kTitle }, and we are at depth = 2
|
||||||
|
// then this may contain hash(hash("Jacqueline"), "My Cool Album").
|
||||||
|
std::uint64_t components_hash; |
||||||
|
}; |
||||||
|
Header header; |
||||||
|
|
||||||
|
// The filterable / selectable item that this key represents. "Jacqueline" for
|
||||||
|
// kArtist, "My Cool Album" for kAlbum, etc.
|
||||||
|
std::optional<std::string> item; |
||||||
|
// If this is a leaf component, the track id for this record.
|
||||||
|
// This could reasonably be the value for a record, but we keep it as a part
|
||||||
|
// of the key to help with disambiguation.
|
||||||
|
std::optional<TrackId> track; |
||||||
|
}; |
||||||
|
|
||||||
|
auto Index(const IndexInfo&, const Track&, leveldb::WriteBatch*) -> bool; |
||||||
|
auto ExpandHeader(const IndexKey::Header&, const std::optional<std::string>&) |
||||||
|
-> IndexKey::Header; |
||||||
|
|
||||||
|
// Predefined indexes
|
||||||
|
// TODO(jacqueline): Make these defined at runtime! :)
|
||||||
|
|
||||||
|
extern const IndexInfo kAlbumsByArtist; |
||||||
|
extern const IndexInfo kTracksByGenre; |
||||||
|
extern const IndexInfo kAllTracks; |
||||||
|
|
||||||
|
} // namespace database
|
@ -0,0 +1,88 @@ |
|||||||
|
/*
|
||||||
|
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: GPL-3.0-only |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "index.hpp" |
||||||
|
#include <stdint.h> |
||||||
|
#include <variant> |
||||||
|
#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}, |
||||||
|
}; |
||||||
|
|
||||||
|
auto Index(const IndexInfo& info, const Track& t, leveldb::WriteBatch* batch) |
||||||
|
-> bool { |
||||||
|
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)); |
||||||
|
if (text) { |
||||||
|
key.item = *text; |
||||||
|
} else { |
||||||
|
key.item = {}; |
||||||
|
} |
||||||
|
|
||||||
|
// If this is the last component, then we should also fill in the track id.
|
||||||
|
if (i == info.components.size() - 1) { |
||||||
|
key.track = t.data().id(); |
||||||
|
} else { |
||||||
|
key.track = {}; |
||||||
|
} |
||||||
|
|
||||||
|
auto encoded = EncodeIndexKey(key); |
||||||
|
batch->Put(encoded.slice, leveldb::Slice{}); |
||||||
|
|
||||||
|
// 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 true; |
||||||
|
} |
||||||
|
|
||||||
|
auto ExpandHeader(const IndexKey::Header& header, |
||||||
|
const std::optional<std::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
|
Loading…
Reference in new issue