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