parent
2be4d4204c
commit
083f4011aa
@ -1,87 +0,0 @@ |
|||||||
#pragma once |
|
||||||
|
|
||||||
#include <cstddef> |
|
||||||
#include <cstdint> |
|
||||||
#include <memory> |
|
||||||
#include <optional> |
|
||||||
#include <string> |
|
||||||
#include <utility> |
|
||||||
|
|
||||||
#include "esp32/himem.h" |
|
||||||
#include "ff.h" |
|
||||||
#include "span.hpp" |
|
||||||
#include "sys/_stdint.h" |
|
||||||
|
|
||||||
namespace database { |
|
||||||
|
|
||||||
// Types used for indexing into files on disk. These should, at minimum, match
|
|
||||||
// the size of the types that the underlying filesystem uses to address within
|
|
||||||
// files. FAT32 uses 32 bit address. If we drop this and just support exFAT, we
|
|
||||||
// can change these to 64 bit types.
|
|
||||||
typedef uint32_t Index_t; |
|
||||||
typedef Index_t IndexOffset_t; |
|
||||||
|
|
||||||
// The amount of memory that will be used to page database columns in from disk.
|
|
||||||
// Currently we only use a single 'page' in PSRAM per column, but with some
|
|
||||||
// refactoring we could easily page more.
|
|
||||||
// Keep this value 32KiB-aligned for himem compatibility.
|
|
||||||
extern const std::size_t kRamBlockSize; |
|
||||||
|
|
||||||
struct DatabaseHeader { |
|
||||||
uint32_t magic_number; |
|
||||||
uint16_t db_version; |
|
||||||
Index_t num_indices; |
|
||||||
}; |
|
||||||
|
|
||||||
struct DatabaseEntry { |
|
||||||
uint8_t type; |
|
||||||
std::string path; |
|
||||||
|
|
||||||
std::string title; |
|
||||||
std::string album; |
|
||||||
std::string artist; |
|
||||||
std::string album_artist; |
|
||||||
}; |
|
||||||
|
|
||||||
struct IndexEntry { |
|
||||||
uint8_t type; |
|
||||||
IndexOffset_t path; |
|
||||||
|
|
||||||
IndexOffset_t title; |
|
||||||
IndexOffset_t album; |
|
||||||
IndexOffset_t artist; |
|
||||||
IndexOffset_t album_artist; |
|
||||||
}; |
|
||||||
|
|
||||||
struct RowData { |
|
||||||
std::unique_ptr<std::byte[]> arr; |
|
||||||
std::size_t length; |
|
||||||
}; |
|
||||||
|
|
||||||
// Representation of a single column of data. Each column is simply a tightly
|
|
||||||
// packed list of [size, [bytes, ...]] pairs. Callers are responsible for
|
|
||||||
// parsing and encoding the actual bytes themselves.
|
|
||||||
class Column { |
|
||||||
public: |
|
||||||
static auto Open(std::string) -> std::optional<Column>; |
|
||||||
|
|
||||||
Column(FIL file, std::size_t file_size); |
|
||||||
~Column(); |
|
||||||
|
|
||||||
auto ReadDataAtOffset(esp_himem_rangehandle_t, IndexOffset_t) -> RowData; |
|
||||||
auto AppendRow(cpp::span<std::byte> row) -> bool; |
|
||||||
auto FlushChanges() -> void; |
|
||||||
|
|
||||||
private: |
|
||||||
FIL file_; |
|
||||||
IndexOffset_t length_; |
|
||||||
|
|
||||||
esp_himem_handle_t block_; |
|
||||||
std::pair<IndexOffset_t, IndexOffset_t> loaded_range_; |
|
||||||
|
|
||||||
auto IsOffsetLoaded(IndexOffset_t offset) -> bool; |
|
||||||
auto LoadOffsetFromDisk(cpp::span<std::byte> dest, IndexOffset_t offset) |
|
||||||
-> bool; |
|
||||||
}; |
|
||||||
|
|
||||||
} // namespace database
|
|
@ -1,53 +0,0 @@ |
|||||||
#pragma once |
|
||||||
|
|
||||||
#include <string> |
|
||||||
|
|
||||||
#include "result.hpp" |
|
||||||
#include "span.hpp" |
|
||||||
|
|
||||||
#include "table.hpp" |
|
||||||
|
|
||||||
namespace database { |
|
||||||
|
|
||||||
class TableReader { |
|
||||||
public: |
|
||||||
enum ReadError { |
|
||||||
OUT_OF_RANGE, |
|
||||||
IO_ERROR, |
|
||||||
PARSE_ERROR, |
|
||||||
}; |
|
||||||
|
|
||||||
auto ReadEntryAtIndex(Index_t index) -> cpp::result<DatabaseEntry, ReadError>; |
|
||||||
|
|
||||||
template <typename T> |
|
||||||
auto ReadColumnOffsetAtIndex(Column<T> col, Index_t index) |
|
||||||
-> cpp::result<IndexOffset_t, ReadError>; |
|
||||||
|
|
||||||
template <typename T> |
|
||||||
auto ParseColumnAtIndex(Column<T> col, Index_t index) |
|
||||||
-> cpp::result<T, ReadError> { |
|
||||||
return ReadColumnOffsetAtIndex(col, index).map([&](IndexOffset_t offset) { |
|
||||||
return ReadColumnAtOffset(col, offset); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
template <typename T> |
|
||||||
auto ParseColumnAtOffset(Column<T> col, IndexOffset_t offset) |
|
||||||
-> cpp::result<T, ReadError> { |
|
||||||
return ReadDataAtOffset(col.Filename(), offset) |
|
||||||
.flat_map([&](cpp::span<std::byte> data) { |
|
||||||
auto res = = col.ParseValue(data); |
|
||||||
if (res) { |
|
||||||
return *res; |
|
||||||
} else { |
|
||||||
return cpp::fail(PARSE_ERROR); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
private: |
|
||||||
auto ReadDataAtOffset(std::string filename, IndexOffset_t offset) |
|
||||||
-> cpp::span<std::byte>; |
|
||||||
}; |
|
||||||
|
|
||||||
} // namespace database
|
|
@ -1,5 +0,0 @@ |
|||||||
#pragma once |
|
||||||
|
|
||||||
#include "table.hpp" |
|
||||||
|
|
||||||
namespace database {} // namespace database
|
|
@ -1,137 +0,0 @@ |
|||||||
#include "table.hpp" |
|
||||||
|
|
||||||
#include <memory> |
|
||||||
#include <optional> |
|
||||||
|
|
||||||
#include "esp32/himem.h" |
|
||||||
#include "esp_err.h" |
|
||||||
#include "ff.h" |
|
||||||
|
|
||||||
namespace database { |
|
||||||
|
|
||||||
const std::size_t kRamBlockSize = 32 * 1024; |
|
||||||
|
|
||||||
auto Column::Open(std::string path) -> std::optional<Column> { |
|
||||||
FILINFO info; |
|
||||||
FRESULT res = f_stat(path.c_str(), &info); |
|
||||||
if (res != FR_OK) { |
|
||||||
return {}; |
|
||||||
} |
|
||||||
|
|
||||||
FIL file; |
|
||||||
res = f_open(&file, path.c_str(), FA_READ | FA_WRITE); |
|
||||||
if (res != FR_OK) { |
|
||||||
return {}; |
|
||||||
} |
|
||||||
|
|
||||||
return std::make_optional<Column>(file, info.fsize); |
|
||||||
} |
|
||||||
|
|
||||||
Column::Column(FIL file, std::size_t file_size) |
|
||||||
: file_(file), length_(file_size), loaded_range_(0, 0) { |
|
||||||
ESP_ERROR_CHECK(esp_himem_alloc(kRamBlockSize, &block_)); |
|
||||||
} |
|
||||||
|
|
||||||
Column::~Column() { |
|
||||||
f_close(&file_); |
|
||||||
esp_himem_free(block_); |
|
||||||
} |
|
||||||
|
|
||||||
auto Column::ReadDataAtOffset(esp_himem_rangehandle_t range, |
|
||||||
IndexOffset_t offset) -> RowData { |
|
||||||
// To start, we always need to map our address space.
|
|
||||||
std::byte* paged_block; |
|
||||||
esp_himem_map(block_, range, 0, 0, kRamBlockSize, 0, |
|
||||||
reinterpret_cast<void**>(&paged_block)); |
|
||||||
|
|
||||||
// Next, we need to see how long the data we're returning is. This might
|
|
||||||
// already exist in memory.
|
|
||||||
if (!IsOffsetLoaded(offset) || |
|
||||||
!IsOffsetLoaded(offset + sizeof(std::size_t))) { |
|
||||||
LoadOffsetFromDisk({paged_block, kRamBlockSize}, offset); |
|
||||||
} |
|
||||||
|
|
||||||
IndexOffset_t paged_offset = offset - loaded_range_.first; |
|
||||||
std::size_t data_size = |
|
||||||
*(reinterpret_cast<std::size_t*>(paged_block + paged_offset)); |
|
||||||
|
|
||||||
// Now that we have the size, we need to do the same thing again to get the
|
|
||||||
// real data. Hopefully this doesn't require an actual second disk read, since
|
|
||||||
// LoadOffsetFromDisk should load a generous amount after the offset we
|
|
||||||
// previously gave.
|
|
||||||
if (!IsOffsetLoaded(offset) || !IsOffsetLoaded(offset + data_size)) { |
|
||||||
LoadOffsetFromDisk({paged_block, kRamBlockSize}, offset); |
|
||||||
} |
|
||||||
|
|
||||||
paged_offset = offset - loaded_range_.first + sizeof(IndexOffset_t); |
|
||||||
cpp::span<std::byte> src(paged_block + paged_offset, data_size); |
|
||||||
|
|
||||||
auto res = std::make_unique<std::byte[]>(data_size); |
|
||||||
cpp::span<std::byte> dest(res.get(), data_size); |
|
||||||
|
|
||||||
std::copy(src.begin(), src.end(), dest.begin()); |
|
||||||
|
|
||||||
// Finally, unmap from the range we were given to return it to its initial
|
|
||||||
// state.
|
|
||||||
esp_himem_unmap(range, paged_block, kRamBlockSize); |
|
||||||
|
|
||||||
return {std::move(res), data_size}; |
|
||||||
} |
|
||||||
|
|
||||||
auto Column::AppendRow(cpp::span<std::byte> row) -> bool { |
|
||||||
FRESULT res = f_lseek(&file_, length_); |
|
||||||
if (res != FR_OK) { |
|
||||||
// TODO(jacqueline): Handle errors.
|
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
std::size_t bytes_written = 0; |
|
||||||
std::size_t length = row.size_bytes(); |
|
||||||
res = f_write(&file_, &length, sizeof(std::size_t), &bytes_written); |
|
||||||
if (res != FR_OK || bytes_written != sizeof(std::size_t)) { |
|
||||||
// TODO(jacqueline): Handle errors.
|
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
res = f_write(&file_, row.data(), length, &bytes_written); |
|
||||||
if (res != FR_OK || bytes_written != length) { |
|
||||||
// TODO(jacqueline): Handle errors.
|
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
length_ += sizeof(std::size_t) + row.size_bytes(); |
|
||||||
|
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
auto Column::FlushChanges() -> void { |
|
||||||
f_sync(&file_); |
|
||||||
} |
|
||||||
|
|
||||||
auto Column::IsOffsetLoaded(IndexOffset_t offset) -> bool { |
|
||||||
return loaded_range_.first <= offset && |
|
||||||
loaded_range_.second > offset + sizeof(std::size_t); |
|
||||||
} |
|
||||||
|
|
||||||
auto Column::LoadOffsetFromDisk(cpp::span<std::byte> dest, IndexOffset_t offset) |
|
||||||
-> bool { |
|
||||||
FRESULT res = f_lseek(&file_, offset); |
|
||||||
if (res != FR_OK) { |
|
||||||
// TODO(jacqueline): Handle errors.
|
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
UINT bytes_read = 0; |
|
||||||
res = f_read(&file_, dest.data(), dest.size(), &bytes_read); |
|
||||||
if (res != FR_OK) { |
|
||||||
// TODO(jacqueline): Handle errors.
|
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
loaded_range_.first = offset; |
|
||||||
loaded_range_.second = offset + bytes_read; |
|
||||||
|
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
} // namespace database
|
|
Loading…
Reference in new issue