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