Store the current collator in the database

custom
jacqueline 1 year ago
parent b58c081508
commit 4cc5fa4c9c
  1. 60
      src/database/database.cpp
  2. 15
      src/locale/collation.cpp
  3. 32
      src/locale/include/collation.hpp

@ -28,6 +28,7 @@
#include "leveldb/iterator.h" #include "leveldb/iterator.h"
#include "leveldb/options.h" #include "leveldb/options.h"
#include "leveldb/slice.h" #include "leveldb/slice.h"
#include "leveldb/status.h"
#include "leveldb/write_batch.h" #include "leveldb/write_batch.h"
#include "db_events.hpp" #include "db_events.hpp"
@ -51,12 +52,13 @@ static const char kDbPath[] = "/.tangara-db";
static const char kKeyDbVersion[] = "schema_version"; static const char kKeyDbVersion[] = "schema_version";
static const uint8_t kCurrentDbVersion = 3; static const uint8_t kCurrentDbVersion = 3;
static const char kKeyCollator[] = "collator";
static const char kKeyTrackId[] = "next_track_id"; static const char kKeyTrackId[] = "next_track_id";
static std::atomic<bool> sIsDbOpen(false); 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(); Database::Destroy();
leveldb::DB* db; leveldb::DB* db;
options.create_if_missing = true; options.create_if_missing = true;
@ -71,9 +73,48 @@ static auto CreateNewDatabase(leveldb::Options& options) -> leveldb::DB* {
delete db; delete db;
return nullptr; 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; 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, auto Database::Open(IFileGatherer& gatherer,
ITagParser& parser, ITagParser& parser,
locale::ICollator& collator) locale::ICollator& collator)
@ -107,23 +148,16 @@ auto Database::Open(IFileGatherer& gatherer,
auto status = leveldb::DB::Open(options, kDbPath, &db); auto status = leveldb::DB::Open(options, kDbPath, &db);
if (!status.ok()) { if (!status.ok()) {
ESP_LOGI(kTag, "opening db failed. recreating."); ESP_LOGI(kTag, "opening db failed. recreating.");
db = CreateNewDatabase(options); db = CreateNewDatabase(options, collator);
if (db == nullptr) { if (db == nullptr) {
return cpp::fail(FAILED_TO_OPEN); return cpp::fail(FAILED_TO_OPEN);
} }
} }
std::string raw_version; if (!CheckDatabase(*db, collator)) {
std::optional<uint8_t> version{}; ESP_LOGI(kTag, "db incompatible. recreating.");
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.");
delete db; delete db;
db = CreateNewDatabase(options); db = CreateNewDatabase(options, collator);
if (db == nullptr) { if (db == nullptr) {
return cpp::fail(FAILED_TO_OPEN); return cpp::fail(FAILED_TO_OPEN);
} }

@ -58,19 +58,26 @@ auto GLibCollator::create() -> GLibCollator* {
return nullptr; 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>(); 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_LOGE(kTag, "parsing locale data failed");
esp_partition_munmap(handle); esp_partition_munmap(handle);
return nullptr; 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) 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() { GLibCollator::~GLibCollator() {
esp_partition_munmap(handle_); esp_partition_munmap(handle_);

@ -8,6 +8,7 @@
#include <cstddef> #include <cstddef>
#include <memory> #include <memory>
#include <optional>
#include <string> #include <string>
#include "esp_partition.h" #include "esp_partition.h"
@ -17,31 +18,56 @@
namespace locale { 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 { class ICollator {
public: public:
virtual ~ICollator() {} 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; 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 { class NoopCollator : public ICollator {
public: public:
auto Describe() -> std::optional<std::string> override { return {}; }
auto Transform(const std::string& in) -> std::string override { return in; } 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 { class GLibCollator : public ICollator {
public: public:
static auto create() -> GLibCollator*; static auto create() -> GLibCollator*;
~GLibCollator(); ~GLibCollator();
auto Describe() -> std::optional<std::string> override { return name_; }
auto Transform(const std::string& in) -> std::string override; auto Transform(const std::string& in) -> std::string override;
private: 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>); std::unique_ptr<locale_data_t>);
const std::string name_;
const esp_partition_mmap_handle_t handle_; const esp_partition_mmap_handle_t handle_;
std::unique_ptr<locale_data_t> locale_data_; std::unique_ptr<locale_data_t> locale_data_;
}; };

Loading…
Cancel
Save