parent
fe19478e0f
commit
16e6180ba7
@ -0,0 +1,32 @@ |
||||
#pragma once |
||||
|
||||
#include <leveldb/db.h> |
||||
#include <stdint.h> |
||||
#include <string> |
||||
|
||||
#include "leveldb/slice.h" |
||||
#include "song.hpp" |
||||
|
||||
namespace database { |
||||
|
||||
class OwningSlice { |
||||
public: |
||||
std::string data; |
||||
leveldb::Slice slice; |
||||
|
||||
explicit OwningSlice(std::string d); |
||||
}; |
||||
|
||||
auto CreateDataPrefix() -> OwningSlice; |
||||
auto CreateDataKey(const SongId& id) -> OwningSlice; |
||||
auto CreateDataValue(const SongData& song) -> OwningSlice; |
||||
auto ParseDataValue(const leveldb::Slice& slice) -> std::optional<SongData>; |
||||
|
||||
auto CreateHashKey(const uint64_t& hash) -> OwningSlice; |
||||
auto ParseHashValue(const leveldb::Slice&) -> std::optional<SongId>; |
||||
auto CreateHashValue(SongId id) -> OwningSlice; |
||||
|
||||
auto SongIdToBytes(SongId id) -> OwningSlice; |
||||
auto BytesToSongId(const std::string& bytes) -> SongId; |
||||
|
||||
} // namespace database
|
@ -0,0 +1,76 @@ |
||||
#pragma once |
||||
|
||||
#include <stdint.h> |
||||
#include <cstdint> |
||||
#include <optional> |
||||
#include <string> |
||||
|
||||
#include "leveldb/db.h" |
||||
#include "span.hpp" |
||||
|
||||
namespace database { |
||||
|
||||
typedef uint32_t SongId; |
||||
|
||||
enum Encoding { ENC_UNSUPPORTED, ENC_MP3 }; |
||||
|
||||
struct SongTags { |
||||
Encoding encoding; |
||||
std::optional<std::string> title; |
||||
std::optional<std::string> artist; |
||||
std::optional<std::string> album; |
||||
auto Hash() const -> uint64_t; |
||||
}; |
||||
|
||||
auto ReadAndParseTags(const std::string& path, SongTags* out) -> bool; |
||||
|
||||
class SongData { |
||||
private: |
||||
const SongId id_; |
||||
const std::string filepath_; |
||||
const uint64_t tags_hash_; |
||||
const uint32_t play_count_; |
||||
const bool is_tombstoned_; |
||||
|
||||
public: |
||||
SongData(SongId id, const std::string& path, uint64_t hash) |
||||
: id_(id), |
||||
filepath_(path), |
||||
tags_hash_(hash), |
||||
play_count_(0), |
||||
is_tombstoned_(false) {} |
||||
SongData(SongId id, |
||||
const std::string& path, |
||||
uint64_t hash, |
||||
uint32_t play_count, |
||||
bool is_tombstoned) |
||||
: id_(id), |
||||
filepath_(path), |
||||
tags_hash_(hash), |
||||
play_count_(play_count), |
||||
is_tombstoned_(is_tombstoned) {} |
||||
|
||||
auto id() const -> SongId { return id_; } |
||||
auto filepath() const -> std::string { return filepath_; } |
||||
auto play_count() const -> uint32_t { return play_count_; } |
||||
auto tags_hash() const -> uint64_t { return tags_hash_; } |
||||
auto is_tombstoned() const -> bool { return is_tombstoned_; } |
||||
|
||||
auto UpdateHash(uint64_t new_hash) const -> SongData; |
||||
auto Entomb() const -> SongData; |
||||
auto Exhume(const std::string& new_path) const -> SongData; |
||||
}; |
||||
|
||||
class Song { |
||||
public: |
||||
Song(SongData data, SongTags tags) : data_(data), tags_(tags) {} |
||||
|
||||
auto data() -> const SongData& { return data_; } |
||||
auto tags() -> const SongTags& { return tags_; } |
||||
|
||||
private: |
||||
const SongData data_; |
||||
const SongTags tags_; |
||||
}; |
||||
|
||||
} // namespace database
|
@ -1,16 +0,0 @@ |
||||
#pragma once |
||||
|
||||
#include <string> |
||||
|
||||
namespace database { |
||||
|
||||
struct FileInfo { |
||||
bool is_playable; |
||||
std::string artist; |
||||
std::string album; |
||||
std::string title; |
||||
}; |
||||
|
||||
auto GetInfo(const std::string& path, FileInfo* out) -> bool; |
||||
|
||||
} // namespace database
|
@ -0,0 +1,203 @@ |
||||
#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
|
Loading…
Reference in new issue