You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
204 lines
5.4 KiB
204 lines
5.4 KiB
2 years ago
|
#include "records.hpp"
|
||
|
|
||
|
#include <cbor.h>
|
||
|
#include <esp_log.h>
|
||
|
#include <stdint.h>
|
||
|
|
||
|
#include <sstream>
|
||
|
#include <vector>
|
||
|
|
||
|
#include "song.hpp"
|
||
|
|
||
|
namespace database {
|
||
|
|
||
|
static const char* kTag = "RECORDS";
|
||
|
|
||
|
static const char kDataPrefix = 'D';
|
||
|
static const char kHashPrefix = 'H';
|
||
|
static const char kFieldSeparator = '\0';
|
||
|
|
||
|
template <typename T>
|
||
|
auto cbor_encode(uint8_t** out_buf, T fn) -> std::size_t {
|
||
|
CborEncoder size_encoder;
|
||
|
cbor_encoder_init(&size_encoder, NULL, 0, 0);
|
||
|
std::invoke(fn, &size_encoder);
|
||
|
std::size_t buf_size = cbor_encoder_get_extra_bytes_needed(&size_encoder);
|
||
|
*out_buf = new uint8_t[buf_size];
|
||
|
|
||
|
CborEncoder encoder;
|
||
|
cbor_encoder_init(&encoder, *out_buf, buf_size, 0);
|
||
|
std::invoke(fn, &encoder);
|
||
|
|
||
|
return buf_size;
|
||
|
}
|
||
|
|
||
|
OwningSlice::OwningSlice(std::string d) : data(d), slice(data) {}
|
||
|
|
||
|
auto CreateDataPrefix() -> OwningSlice {
|
||
|
char data[2] = {kDataPrefix, kFieldSeparator};
|
||
|
return OwningSlice({data, 2});
|
||
|
}
|
||
|
|
||
|
auto CreateDataKey(const SongId& id) -> OwningSlice {
|
||
|
std::ostringstream output;
|
||
|
output.put(kDataPrefix).put(kFieldSeparator);
|
||
|
output << SongIdToBytes(id).data;
|
||
|
return OwningSlice(output.str());
|
||
|
}
|
||
|
|
||
|
auto CreateDataValue(const SongData& song) -> OwningSlice {
|
||
|
uint8_t* buf;
|
||
|
std::size_t buf_len = cbor_encode(&buf, [&](CborEncoder* enc) {
|
||
|
CborEncoder array_encoder;
|
||
|
CborError err;
|
||
|
err = cbor_encoder_create_array(enc, &array_encoder, 5);
|
||
|
if (err != CborNoError && err != CborErrorOutOfMemory) {
|
||
|
ESP_LOGE(kTag, "encoding err %u", err);
|
||
|
return;
|
||
|
}
|
||
|
err = cbor_encode_int(&array_encoder, song.id());
|
||
|
if (err != CborNoError && err != CborErrorOutOfMemory) {
|
||
|
ESP_LOGE(kTag, "encoding err %u", err);
|
||
|
return;
|
||
|
}
|
||
|
err = cbor_encode_text_string(&array_encoder, song.filepath().c_str(),
|
||
|
song.filepath().size());
|
||
|
if (err != CborNoError && err != CborErrorOutOfMemory) {
|
||
|
ESP_LOGE(kTag, "encoding err %u", err);
|
||
|
return;
|
||
|
}
|
||
|
err = cbor_encode_uint(&array_encoder, song.tags_hash());
|
||
|
if (err != CborNoError && err != CborErrorOutOfMemory) {
|
||
|
ESP_LOGE(kTag, "encoding err %u", err);
|
||
|
return;
|
||
|
}
|
||
|
err = cbor_encode_int(&array_encoder, song.play_count());
|
||
|
if (err != CborNoError && err != CborErrorOutOfMemory) {
|
||
|
ESP_LOGE(kTag, "encoding err %u", err);
|
||
|
return;
|
||
|
}
|
||
|
err = cbor_encode_boolean(&array_encoder, song.is_tombstoned());
|
||
|
if (err != CborNoError && err != CborErrorOutOfMemory) {
|
||
|
ESP_LOGE(kTag, "encoding err %u", err);
|
||
|
return;
|
||
|
}
|
||
|
err = cbor_encoder_close_container(enc, &array_encoder);
|
||
|
if (err != CborNoError && err != CborErrorOutOfMemory) {
|
||
|
ESP_LOGE(kTag, "encoding err %u", err);
|
||
|
return;
|
||
|
}
|
||
|
});
|
||
|
std::string as_str(reinterpret_cast<char*>(buf), buf_len);
|
||
|
delete buf;
|
||
|
return OwningSlice(as_str);
|
||
|
}
|
||
|
|
||
|
auto ParseDataValue(const leveldb::Slice& slice) -> std::optional<SongData> {
|
||
|
CborParser parser;
|
||
|
CborValue container;
|
||
|
CborError err;
|
||
|
err = cbor_parser_init(reinterpret_cast<const uint8_t*>(slice.data()),
|
||
|
slice.size(), 0, &parser, &container);
|
||
|
if (err != CborNoError) {
|
||
|
return {};
|
||
|
}
|
||
|
|
||
|
CborValue val;
|
||
|
err = cbor_value_enter_container(&container, &val);
|
||
|
if (err != CborNoError) {
|
||
|
return {};
|
||
|
}
|
||
|
|
||
|
uint64_t raw_int;
|
||
|
err = cbor_value_get_uint64(&val, &raw_int);
|
||
|
if (err != CborNoError) {
|
||
|
return {};
|
||
|
}
|
||
|
SongId id = raw_int;
|
||
|
err = cbor_value_advance(&val);
|
||
|
if (err != CborNoError) {
|
||
|
return {};
|
||
|
}
|
||
|
|
||
|
char* raw_path;
|
||
|
std::size_t len;
|
||
|
err = cbor_value_dup_text_string(&val, &raw_path, &len, &val);
|
||
|
if (err != CborNoError) {
|
||
|
return {};
|
||
|
}
|
||
|
std::string path(raw_path, len);
|
||
|
delete raw_path;
|
||
|
|
||
|
err = cbor_value_get_uint64(&val, &raw_int);
|
||
|
if (err != CborNoError) {
|
||
|
return {};
|
||
|
}
|
||
|
uint64_t hash = raw_int;
|
||
|
err = cbor_value_advance(&val);
|
||
|
if (err != CborNoError) {
|
||
|
return {};
|
||
|
}
|
||
|
|
||
|
err = cbor_value_get_uint64(&val, &raw_int);
|
||
|
if (err != CborNoError) {
|
||
|
return {};
|
||
|
}
|
||
|
uint32_t play_count = raw_int;
|
||
|
err = cbor_value_advance(&val);
|
||
|
if (err != CborNoError) {
|
||
|
return {};
|
||
|
}
|
||
|
|
||
|
bool is_tombstoned;
|
||
|
err = cbor_value_get_boolean(&val, &is_tombstoned);
|
||
|
if (err != CborNoError) {
|
||
|
return {};
|
||
|
}
|
||
|
|
||
|
return SongData(id, path, hash, play_count, is_tombstoned);
|
||
|
}
|
||
|
|
||
|
auto CreateHashKey(const uint64_t& hash) -> OwningSlice {
|
||
|
std::ostringstream output;
|
||
|
output.put(kHashPrefix).put(kFieldSeparator);
|
||
|
|
||
|
uint8_t buf[16];
|
||
|
CborEncoder enc;
|
||
|
cbor_encoder_init(&enc, buf, sizeof(buf), 0);
|
||
|
cbor_encode_uint(&enc, hash);
|
||
|
std::size_t len = cbor_encoder_get_buffer_size(&enc, buf);
|
||
|
output.write(reinterpret_cast<char*>(buf), len);
|
||
|
|
||
|
return OwningSlice(output.str());
|
||
|
}
|
||
|
|
||
|
auto ParseHashValue(const leveldb::Slice& slice) -> std::optional<SongId> {
|
||
|
return BytesToSongId(slice.ToString());
|
||
|
}
|
||
|
|
||
|
auto CreateHashValue(SongId id) -> OwningSlice {
|
||
|
return SongIdToBytes(id);
|
||
|
}
|
||
|
|
||
|
auto SongIdToBytes(SongId id) -> OwningSlice {
|
||
|
uint8_t buf[8];
|
||
|
CborEncoder enc;
|
||
|
cbor_encoder_init(&enc, buf, sizeof(buf), 0);
|
||
|
cbor_encode_uint(&enc, id);
|
||
|
std::size_t len = cbor_encoder_get_buffer_size(&enc, buf);
|
||
|
std::string as_str(reinterpret_cast<char*>(buf), len);
|
||
|
return OwningSlice(as_str);
|
||
|
}
|
||
|
|
||
|
auto BytesToSongId(const std::string& bytes) -> SongId {
|
||
|
CborParser parser;
|
||
|
CborValue val;
|
||
|
cbor_parser_init(reinterpret_cast<const uint8_t*>(bytes.data()), bytes.size(),
|
||
|
0, &parser, &val);
|
||
|
uint64_t raw_id;
|
||
|
cbor_value_get_uint64(&val, &raw_id);
|
||
|
return raw_id;
|
||
|
}
|
||
|
|
||
|
} // namespace database
|