Store the current collator in the database
This commit is contained in:
+47
-13
@@ -28,6 +28,7 @@
|
||||
#include "leveldb/iterator.h"
|
||||
#include "leveldb/options.h"
|
||||
#include "leveldb/slice.h"
|
||||
#include "leveldb/status.h"
|
||||
#include "leveldb/write_batch.h"
|
||||
|
||||
#include "db_events.hpp"
|
||||
@@ -51,12 +52,13 @@ static const char kDbPath[] = "/.tangara-db";
|
||||
|
||||
static const char kKeyDbVersion[] = "schema_version";
|
||||
static const uint8_t kCurrentDbVersion = 3;
|
||||
|
||||
static const char kKeyCollator[] = "collator";
|
||||
static const char kKeyTrackId[] = "next_track_id";
|
||||
|
||||
static std::atomic<bool> sIsDbOpen(false);
|
||||
|
||||
static auto CreateNewDatabase(leveldb::Options& options) -> leveldb::DB* {
|
||||
static auto CreateNewDatabase(leveldb::Options& options, locale::ICollator& col)
|
||||
-> leveldb::DB* {
|
||||
Database::Destroy();
|
||||
leveldb::DB* db;
|
||||
options.create_if_missing = true;
|
||||
@@ -71,9 +73,48 @@ static auto CreateNewDatabase(leveldb::Options& options) -> leveldb::DB* {
|
||||
delete db;
|
||||
return nullptr;
|
||||
}
|
||||
ESP_LOGI(kTag, "opening db with collator %s",
|
||||
col.Describe().value_or("NULL").c_str());
|
||||
status = db->Put(leveldb::WriteOptions{}, kKeyCollator,
|
||||
col.Describe().value_or(""));
|
||||
if (!status.ok()) {
|
||||
delete db;
|
||||
return nullptr;
|
||||
}
|
||||
return db;
|
||||
}
|
||||
|
||||
static auto CheckDatabase(leveldb::DB& db, locale::ICollator& col) -> bool {
|
||||
leveldb::Status status;
|
||||
|
||||
std::string raw_version;
|
||||
std::optional<uint8_t> version{};
|
||||
status = db.Get(leveldb::ReadOptions{}, kKeyDbVersion, &raw_version);
|
||||
if (status.ok()) {
|
||||
version = std::stoi(raw_version);
|
||||
}
|
||||
if (!version || *version != kCurrentDbVersion) {
|
||||
ESP_LOGW(kTag, "db version missing or incorrect");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string collator;
|
||||
status = db.Get(leveldb::ReadOptions{}, kKeyCollator, &collator);
|
||||
if (!status.ok()) {
|
||||
ESP_LOGW(kTag, "db collator is unknown");
|
||||
return false;
|
||||
}
|
||||
auto needed = col.Describe();
|
||||
|
||||
if ((needed && needed.value() != collator) ||
|
||||
(!needed && !collator.empty())) {
|
||||
ESP_LOGW(kTag, "db collator is mismatched");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Database::Open(IFileGatherer& gatherer,
|
||||
ITagParser& parser,
|
||||
locale::ICollator& collator)
|
||||
@@ -107,23 +148,16 @@ auto Database::Open(IFileGatherer& gatherer,
|
||||
auto status = leveldb::DB::Open(options, kDbPath, &db);
|
||||
if (!status.ok()) {
|
||||
ESP_LOGI(kTag, "opening db failed. recreating.");
|
||||
db = CreateNewDatabase(options);
|
||||
db = CreateNewDatabase(options, collator);
|
||||
if (db == nullptr) {
|
||||
return cpp::fail(FAILED_TO_OPEN);
|
||||
}
|
||||
}
|
||||
|
||||
std::string raw_version;
|
||||
std::optional<uint8_t> version{};
|
||||
status =
|
||||
db->Get(leveldb::ReadOptions{}, kKeyDbVersion, &raw_version);
|
||||
if (status.ok()) {
|
||||
version = std::stoi(raw_version);
|
||||
}
|
||||
if (!version || *version != kCurrentDbVersion) {
|
||||
ESP_LOGI(kTag, "db version missing or incorrect. recreating.");
|
||||
if (!CheckDatabase(*db, collator)) {
|
||||
ESP_LOGI(kTag, "db incompatible. recreating.");
|
||||
delete db;
|
||||
db = CreateNewDatabase(options);
|
||||
db = CreateNewDatabase(options, collator);
|
||||
if (db == nullptr) {
|
||||
return cpp::fail(FAILED_TO_OPEN);
|
||||
}
|
||||
|
||||
@@ -58,19 +58,26 @@ auto GLibCollator::create() -> GLibCollator* {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// We reserve the first 8 bytes of the partition for an identifier / name.
|
||||
// Copy it out, then crop the rest of the region so that the LC_COLLATE parser
|
||||
// doesn't see it.
|
||||
std::string name{static_cast<const char*>(region)};
|
||||
region = static_cast<const std::byte*>(region) + 8;
|
||||
|
||||
auto data = std::make_unique<locale_data_t>();
|
||||
if (!parse_locale_data(region, partition->size, data.get())) {
|
||||
if (!parse_locale_data(region, partition->size - 8, data.get())) {
|
||||
ESP_LOGE(kTag, "parsing locale data failed");
|
||||
esp_partition_munmap(handle);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new GLibCollator(handle, std::move(data));
|
||||
return new GLibCollator(name, handle, std::move(data));
|
||||
}
|
||||
|
||||
GLibCollator::GLibCollator(const esp_partition_mmap_handle_t handle,
|
||||
GLibCollator::GLibCollator(const std::string& name,
|
||||
const esp_partition_mmap_handle_t handle,
|
||||
std::unique_ptr<locale_data_t> locale)
|
||||
: handle_(handle), locale_data_(std::move(locale)) {}
|
||||
: name_(name), handle_(handle), locale_data_(std::move(locale)) {}
|
||||
|
||||
GLibCollator::~GLibCollator() {
|
||||
esp_partition_munmap(handle_);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "esp_partition.h"
|
||||
@@ -17,31 +18,56 @@
|
||||
|
||||
namespace locale {
|
||||
|
||||
/*
|
||||
* Interface for sorting database entries.
|
||||
* For performance, our database exclusively orders entries via byte
|
||||
* comparisons of each key. Our collators therefore work by transforming keys
|
||||
* such that a byte-order comparison results in a natural ordering.
|
||||
*/
|
||||
class ICollator {
|
||||
public:
|
||||
virtual ~ICollator() {}
|
||||
|
||||
/*
|
||||
* Returns an identify that uniquely describes this collator. Does not need
|
||||
* to be human readable.
|
||||
*/
|
||||
virtual auto Describe() -> std::optional<std::string> = 0;
|
||||
virtual auto Transform(const std::string&) -> std::string = 0;
|
||||
};
|
||||
|
||||
/* Creates and returns the best available collator. */
|
||||
auto CreateCollator() -> std::unique_ptr<ICollator>;
|
||||
|
||||
/*
|
||||
* Collator that doesn't do anything. Used only when there is no available
|
||||
* locale data.
|
||||
*/
|
||||
class NoopCollator : public ICollator {
|
||||
public:
|
||||
auto Describe() -> std::optional<std::string> override { return {}; }
|
||||
auto Transform(const std::string& in) -> std::string override { return in; }
|
||||
};
|
||||
|
||||
auto CreateCollator() -> std::unique_ptr<ICollator>;
|
||||
|
||||
/*
|
||||
* Collator that uses glibc's `strxfrm` to transform keys. Relies on an
|
||||
* LC_COLLATE file (+ 8 byte name header) flashed on a partition in internal
|
||||
* flash.
|
||||
*/
|
||||
class GLibCollator : public ICollator {
|
||||
public:
|
||||
static auto create() -> GLibCollator*;
|
||||
~GLibCollator();
|
||||
|
||||
auto Describe() -> std::optional<std::string> override { return name_; }
|
||||
auto Transform(const std::string& in) -> std::string override;
|
||||
|
||||
private:
|
||||
GLibCollator(const esp_partition_mmap_handle_t,
|
||||
GLibCollator(const std::string& name,
|
||||
const esp_partition_mmap_handle_t,
|
||||
std::unique_ptr<locale_data_t>);
|
||||
|
||||
const std::string name_;
|
||||
const esp_partition_mmap_handle_t handle_;
|
||||
std::unique_ptr<locale_data_t> locale_data_;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user