summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/database/database.cpp60
-rw-r--r--src/locale/collation.cpp15
-rw-r--r--src/locale/include/collation.hpp32
3 files changed, 87 insertions, 20 deletions
diff --git a/src/database/database.cpp b/src/database/database.cpp
index 9b978b8c..e826f576 100644
--- a/src/database/database.cpp
+++ b/src/database/database.cpp
@@ -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);
}
diff --git a/src/locale/collation.cpp b/src/locale/collation.cpp
index f5e8272a..2d787b5a 100644
--- a/src/locale/collation.cpp
+++ b/src/locale/collation.cpp
@@ -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_);
diff --git a/src/locale/include/collation.hpp b/src/locale/include/collation.hpp
index e8b6fa17..b666860d 100644
--- a/src/locale/include/collation.hpp
+++ b/src/locale/include/collation.hpp
@@ -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_;
};