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/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_;
};

Loading…
Cancel
Save