summaryrefslogtreecommitdiff
path: root/src/database
diff options
context:
space:
mode:
authorAilurux <ailuruxx@gmail.com>2023-06-19 11:21:32 +1000
committerAilurux <ailuruxx@gmail.com>2023-06-19 11:21:32 +1000
commit039272455acddbe446269ea4b6ef66f44f457f1e (patch)
tree1e9a8173aeb4eb027701e89019e9410a9550d3cb /src/database
parent8ce751ad56c7efe19f835e3b6bbb1a843cef9119 (diff)
parent6ff8b5886ef91ed46dba08686900d519f6c9c62d (diff)
downloadtangara-fw-039272455acddbe446269ea4b6ef66f44f457f1e.tar.gz
Merge branch 'main' of https://git.sr.ht/~jacqueline/tangara-fw
Diffstat (limited to 'src/database')
-rw-r--r--src/database/CMakeLists.txt2
-rw-r--r--src/database/database.cpp150
-rw-r--r--src/database/env_esp.cpp5
-rw-r--r--src/database/include/database.hpp26
-rw-r--r--src/database/include/env_esp.hpp2
-rw-r--r--src/database/include/records.hpp36
-rw-r--r--src/database/include/song.hpp166
-rw-r--r--src/database/include/tag_parser.hpp6
-rw-r--r--src/database/include/track.hpp169
-rw-r--r--src/database/records.cpp38
-rw-r--r--src/database/tag_parser.cpp20
-rw-r--r--src/database/test/test_database.cpp80
-rw-r--r--src/database/test/test_records.cpp22
-rw-r--r--src/database/track.cpp (renamed from src/database/song.cpp)22
14 files changed, 390 insertions, 354 deletions
diff --git a/src/database/CMakeLists.txt b/src/database/CMakeLists.txt
index 211a63cd..e7b1f62c 100644
--- a/src/database/CMakeLists.txt
+++ b/src/database/CMakeLists.txt
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-only
idf_component_register(
- SRCS "env_esp.cpp" "database.cpp" "song.cpp" "records.cpp" "file_gatherer.cpp" "tag_parser.cpp"
+ SRCS "env_esp.cpp" "database.cpp" "track.cpp" "records.cpp" "file_gatherer.cpp" "tag_parser.cpp"
INCLUDE_DIRS "include"
REQUIRES "result" "span" "esp_psram" "fatfs" "libtags" "komihash" "cbor" "tasks")
diff --git a/src/database/database.cpp b/src/database/database.cpp
index 71954bbb..ac5e4873 100644
--- a/src/database/database.cpp
+++ b/src/database/database.cpp
@@ -17,6 +17,7 @@
#include "esp_log.h"
#include "ff.h"
+#include "freertos/projdefs.h"
#include "leveldb/cache.h"
#include "leveldb/db.h"
#include "leveldb/iterator.h"
@@ -28,16 +29,16 @@
#include "file_gatherer.hpp"
#include "records.hpp"
#include "result.hpp"
-#include "song.hpp"
#include "tag_parser.hpp"
#include "tasks.hpp"
+#include "track.hpp"
namespace database {
static SingletonEnv<leveldb::EspEnv> sEnv;
static const char* kTag = "DB";
-static const char kSongIdKey[] = "next_song_id";
+static const char kTrackIdKey[] = "next_track_id";
static std::atomic<bool> sIsDbOpen(false);
@@ -68,12 +69,13 @@ auto Database::Open(IFileGatherer* gatherer, ITagParser* parser)
return cpp::fail(DatabaseError::ALREADY_OPEN);
}
+ leveldb::sBackgroundThread.reset(
+ tasks::Worker::Start<tasks::Type::kDatabaseBackground>());
std::shared_ptr<tasks::Worker> worker(
tasks::Worker::Start<tasks::Type::kDatabase>());
- leveldb::sBackgroundThread = std::weak_ptr<tasks::Worker>(worker);
return worker
->Dispatch<cpp::result<Database*, DatabaseError>>(
- [&]() -> cpp::result<Database*, DatabaseError> {
+ [=]() -> cpp::result<Database*, DatabaseError> {
leveldb::DB* db;
leveldb::Cache* cache = leveldb::NewLRUCache(24 * 1024);
leveldb::Options options;
@@ -121,15 +123,15 @@ Database::~Database() {
delete db_;
delete cache_;
- leveldb::sBackgroundThread = std::weak_ptr<tasks::Worker>();
+ leveldb::sBackgroundThread.reset();
sIsDbOpen.store(false);
}
auto Database::Update() -> std::future<void> {
return worker_task_->Dispatch<void>([&]() -> void {
- // Stage 1: verify all existing songs are still valid.
- ESP_LOGI(kTag, "verifying existing songs");
+ // Stage 1: verify all existing tracks are still valid.
+ ESP_LOGI(kTag, "verifying existing tracks");
const leveldb::Snapshot* snapshot = db_->GetSnapshot();
leveldb::ReadOptions read_options;
read_options.fill_cache = false;
@@ -138,8 +140,8 @@ auto Database::Update() -> std::future<void> {
OwningSlice prefix = CreateDataPrefix();
it->Seek(prefix.slice);
while (it->Valid() && it->key().starts_with(prefix.slice)) {
- std::optional<SongData> song = ParseDataValue(it->value());
- if (!song) {
+ std::optional<TrackData> track = ParseDataValue(it->value());
+ if (!track) {
// The value was malformed. Drop this record.
ESP_LOGW(kTag, "dropping malformed metadata");
db_->Delete(leveldb::WriteOptions(), it->key());
@@ -147,33 +149,33 @@ auto Database::Update() -> std::future<void> {
continue;
}
- if (song->is_tombstoned()) {
- ESP_LOGW(kTag, "skipping tombstoned %lx", song->id());
+ if (track->is_tombstoned()) {
+ ESP_LOGW(kTag, "skipping tombstoned %lx", track->id());
it->Next();
continue;
}
- SongTags tags;
- if (!tag_parser_->ReadAndParseTags(song->filepath(), &tags) ||
+ TrackTags tags;
+ if (!tag_parser_->ReadAndParseTags(track->filepath(), &tags) ||
tags.encoding == Encoding::kUnsupported) {
- // We couldn't read the tags for this song. Either they were
+ // We couldn't read the tags for this track. Either they were
// malformed, or perhaps the file is missing. Either way, tombstone
// this record.
- ESP_LOGW(kTag, "entombing missing #%lx", song->id());
- dbPutSongData(song->Entomb());
+ ESP_LOGW(kTag, "entombing missing #%lx", track->id());
+ dbPutTrackData(track->Entomb());
it->Next();
continue;
}
uint64_t new_hash = tags.Hash();
- if (new_hash != song->tags_hash()) {
- // This song's tags have changed. Since the filepath is exactly the
+ if (new_hash != track->tags_hash()) {
+ // This track's tags have changed. Since the filepath is exactly the
// same, we assume this is a legitimate correction. Update the
// database.
- ESP_LOGI(kTag, "updating hash (%llx -> %llx)", song->tags_hash(),
+ ESP_LOGI(kTag, "updating hash (%llx -> %llx)", track->tags_hash(),
new_hash);
- dbPutSongData(song->UpdateHash(new_hash));
- dbPutHash(new_hash, song->id());
+ dbPutTrackData(track->UpdateHash(new_hash));
+ dbPutHash(new_hash, track->id());
}
it->Next();
@@ -182,9 +184,9 @@ auto Database::Update() -> std::future<void> {
db_->ReleaseSnapshot(snapshot);
// Stage 2: search for newly added files.
- ESP_LOGI(kTag, "scanning for new songs");
+ ESP_LOGI(kTag, "scanning for new tracks");
file_gatherer_->FindFiles("", [&](const std::string& path) {
- SongTags tags;
+ TrackTags tags;
if (!tag_parser_->ReadAndParseTags(path, &tags) ||
tags.encoding == Encoding::kUnsupported) {
// No parseable tags; skip this fiile.
@@ -194,32 +196,32 @@ auto Database::Update() -> std::future<void> {
// Check for any existing record with the same hash.
uint64_t hash = tags.Hash();
OwningSlice key = CreateHashKey(hash);
- std::optional<SongId> existing_hash;
+ std::optional<TrackId> existing_hash;
std::string raw_entry;
if (db_->Get(leveldb::ReadOptions(), key.slice, &raw_entry).ok()) {
existing_hash = ParseHashValue(raw_entry);
}
if (!existing_hash) {
- // We've never met this song before! Or we have, but the entry is
- // malformed. Either way, record this as a new song.
- SongId id = dbMintNewSongId();
+ // We've never met this track before! Or we have, but the entry is
+ // malformed. Either way, record this as a new track.
+ TrackId id = dbMintNewTrackId();
ESP_LOGI(kTag, "recording new 0x%lx", id);
- dbPutSong(id, path, hash);
+ dbPutTrack(id, path, hash);
return;
}
- std::optional<SongData> existing_data = dbGetSongData(*existing_hash);
+ std::optional<TrackData> existing_data = dbGetTrackData(*existing_hash);
if (!existing_data) {
// We found a hash that matches, but there's no data record? Weird.
- SongData new_data(*existing_hash, path, hash);
- dbPutSongData(new_data);
+ TrackData new_data(*existing_hash, path, hash);
+ dbPutTrackData(new_data);
return;
}
if (existing_data->is_tombstoned()) {
- ESP_LOGI(kTag, "exhuming song %lu", existing_data->id());
- dbPutSongData(existing_data->Exhume(path));
+ ESP_LOGI(kTag, "exhuming track %lu", existing_data->id());
+ dbPutTrackData(existing_data->Exhume(path));
} else if (existing_data->filepath() != path) {
ESP_LOGW(kTag, "tag hash collision");
}
@@ -227,14 +229,26 @@ auto Database::Update() -> std::future<void> {
});
}
-auto Database::GetSongs(std::size_t page_size) -> std::future<Result<Song>*> {
- return worker_task_->Dispatch<Result<Song>*>([=, this]() -> Result<Song>* {
- Continuation<Song> c{.iterator = nullptr,
- .prefix = CreateDataPrefix().data,
- .start_key = CreateDataPrefix().data,
- .forward = true,
- .was_prev_forward = true,
- .page_size = page_size};
+auto Database::GetTrackPath(TrackId id)
+ -> std::future<std::optional<std::string>> {
+ return worker_task_->Dispatch<std::optional<std::string>>(
+ [=, this]() -> std::optional<std::string> {
+ auto track_data = dbGetTrackData(id);
+ if (track_data) {
+ return track_data->filepath();
+ }
+ return {};
+ });
+}
+
+auto Database::GetTracks(std::size_t page_size) -> std::future<Result<Track>*> {
+ return worker_task_->Dispatch<Result<Track>*>([=, this]() -> Result<Track>* {
+ Continuation<Track> c{.iterator = nullptr,
+ .prefix = CreateDataPrefix().data,
+ .start_key = CreateDataPrefix().data,
+ .forward = true,
+ .was_prev_forward = true,
+ .page_size = page_size};
return dbGetPage(c);
});
}
@@ -260,32 +274,32 @@ auto Database::GetPage(Continuation<T>* c) -> std::future<Result<T>*> {
[=, this]() -> Result<T>* { return dbGetPage(copy); });
}
-template auto Database::GetPage<Song>(Continuation<Song>* c)
- -> std::future<Result<Song>*>;
+template auto Database::GetPage<Track>(Continuation<Track>* c)
+ -> std::future<Result<Track>*>;
template auto Database::GetPage<std::string>(Continuation<std::string>* c)
-> std::future<Result<std::string>*>;
-auto Database::dbMintNewSongId() -> SongId {
- SongId next_id = 1;
+auto Database::dbMintNewTrackId() -> TrackId {
+ TrackId next_id = 1;
std::string val;
- auto status = db_->Get(leveldb::ReadOptions(), kSongIdKey, &val);
+ auto status = db_->Get(leveldb::ReadOptions(), kTrackIdKey, &val);
if (status.ok()) {
- next_id = BytesToSongId(val).value_or(next_id);
+ next_id = BytesToTrackId(val).value_or(next_id);
} else if (!status.IsNotFound()) {
// TODO(jacqueline): Handle this more.
- ESP_LOGE(kTag, "failed to get next song id");
+ ESP_LOGE(kTag, "failed to get next track id");
}
- if (!db_->Put(leveldb::WriteOptions(), kSongIdKey,
- SongIdToBytes(next_id + 1).slice)
+ if (!db_->Put(leveldb::WriteOptions(), kTrackIdKey,
+ TrackIdToBytes(next_id + 1).slice)
.ok()) {
- ESP_LOGE(kTag, "failed to write next song id");
+ ESP_LOGE(kTag, "failed to write next track id");
}
return next_id;
}
-auto Database::dbEntomb(SongId id, uint64_t hash) -> void {
+auto Database::dbEntomb(TrackId id, uint64_t hash) -> void {
OwningSlice key = CreateHashKey(hash);
OwningSlice val = CreateHashValue(id);
if (!db_->Put(leveldb::WriteOptions(), key.slice, val.slice).ok()) {
@@ -293,7 +307,7 @@ auto Database::dbEntomb(SongId id, uint64_t hash) -> void {
}
}
-auto Database::dbPutSongData(const SongData& s) -> void {
+auto Database::dbPutTrackData(const TrackData& s) -> void {
OwningSlice key = CreateDataKey(s.id());
OwningSlice val = CreateDataValue(s);
if (!db_->Put(leveldb::WriteOptions(), key.slice, val.slice).ok()) {
@@ -301,7 +315,7 @@ auto Database::dbPutSongData(const SongData& s) -> void {
}
}
-auto Database::dbGetSongData(SongId id) -> std::optional<SongData> {
+auto Database::dbGetTrackData(TrackId id) -> std::optional<TrackData> {
OwningSlice key = CreateDataKey(id);
std::string raw_val;
if (!db_->Get(leveldb::ReadOptions(), key.slice, &raw_val).ok()) {
@@ -311,7 +325,7 @@ auto Database::dbGetSongData(SongId id) -> std::optional<SongData> {
return ParseDataValue(raw_val);
}
-auto Database::dbPutHash(const uint64_t& hash, SongId i) -> void {
+auto Database::dbPutHash(const uint64_t& hash, TrackId i) -> void {
OwningSlice key = CreateHashKey(hash);
OwningSlice val = CreateHashValue(i);
if (!db_->Put(leveldb::WriteOptions(), key.slice, val.slice).ok()) {
@@ -319,7 +333,7 @@ auto Database::dbPutHash(const uint64_t& hash, SongId i) -> void {
}
}
-auto Database::dbGetHash(const uint64_t& hash) -> std::optional<SongId> {
+auto Database::dbGetHash(const uint64_t& hash) -> std::optional<TrackId> {
OwningSlice key = CreateHashKey(hash);
std::string raw_val;
if (!db_->Get(leveldb::ReadOptions(), key.slice, &raw_val).ok()) {
@@ -329,10 +343,10 @@ auto Database::dbGetHash(const uint64_t& hash) -> std::optional<SongId> {
return ParseHashValue(raw_val);
}
-auto Database::dbPutSong(SongId id,
- const std::string& path,
- const uint64_t& hash) -> void {
- dbPutSongData(SongData(id, path, hash));
+auto Database::dbPutTrack(TrackId id,
+ const std::string& path,
+ const uint64_t& hash) -> void {
+ dbPutTrackData(TrackData(id, path, hash));
dbPutHash(hash, id);
}
@@ -455,24 +469,24 @@ auto Database::dbGetPage(const Continuation<T>& c) -> Result<T>* {
return new Result<T>(std::move(records), next_page, prev_page);
}
-template auto Database::dbGetPage<Song>(const Continuation<Song>& c)
- -> Result<Song>*;
+template auto Database::dbGetPage<Track>(const Continuation<Track>& c)
+ -> Result<Track>*;
template auto Database::dbGetPage<std::string>(
const Continuation<std::string>& c) -> Result<std::string>*;
template <>
-auto Database::ParseRecord<Song>(const leveldb::Slice& key,
- const leveldb::Slice& val)
- -> std::optional<Song> {
- std::optional<SongData> data = ParseDataValue(val);
+auto Database::ParseRecord<Track>(const leveldb::Slice& key,
+ const leveldb::Slice& val)
+ -> std::optional<Track> {
+ std::optional<TrackData> data = ParseDataValue(val);
if (!data || data->is_tombstoned()) {
return {};
}
- SongTags tags;
+ TrackTags tags;
if (!tag_parser_->ReadAndParseTags(data->filepath(), &tags)) {
return {};
}
- return Song(*data, tags);
+ return Track(*data, tags);
}
template <>
diff --git a/src/database/env_esp.cpp b/src/database/env_esp.cpp
index 704e0a54..ad1f2221 100644
--- a/src/database/env_esp.cpp
+++ b/src/database/env_esp.cpp
@@ -15,6 +15,7 @@
#include <cstring>
#include <functional>
#include <limits>
+#include <memory>
#include <mutex>
#include <queue>
#include <set>
@@ -39,7 +40,7 @@
namespace leveldb {
-std::weak_ptr<tasks::Worker> sBackgroundThread;
+std::shared_ptr<tasks::Worker> sBackgroundThread;
std::string ErrToStr(FRESULT err) {
switch (err) {
@@ -463,7 +464,7 @@ EspEnv::EspEnv() {}
void EspEnv::Schedule(
void (*background_work_function)(void* background_work_arg),
void* background_work_arg) {
- auto worker = sBackgroundThread.lock();
+ auto worker = sBackgroundThread;
if (worker) {
worker->Dispatch<void>(
[=]() { std::invoke(background_work_function, background_work_arg); });
diff --git a/src/database/include/database.hpp b/src/database/include/database.hpp
index 5214b8df..8fecc5f6 100644
--- a/src/database/include/database.hpp
+++ b/src/database/include/database.hpp
@@ -23,9 +23,9 @@
#include "leveldb/slice.h"
#include "records.hpp"
#include "result.hpp"
-#include "song.hpp"
#include "tag_parser.hpp"
#include "tasks.hpp"
+#include "track.hpp"
namespace database {
@@ -82,7 +82,9 @@ class Database {
auto Update() -> std::future<void>;
- auto GetSongs(std::size_t page_size) -> std::future<Result<Song>*>;
+ auto GetTrackPath(TrackId id) -> std::future<std::optional<std::string>>;
+
+ auto GetTracks(std::size_t page_size) -> std::future<Result<Track>*>;
auto GetDump(std::size_t page_size) -> std::future<Result<std::string>*>;
template <typename T>
@@ -109,14 +111,14 @@ class Database {
ITagParser* tag_parser,
std::shared_ptr<tasks::Worker> worker);
- auto dbMintNewSongId() -> SongId;
- auto dbEntomb(SongId song, uint64_t hash) -> void;
+ auto dbMintNewTrackId() -> TrackId;
+ auto dbEntomb(TrackId track, uint64_t hash) -> void;
- auto dbPutSongData(const SongData& s) -> void;
- auto dbGetSongData(SongId id) -> std::optional<SongData>;
- auto dbPutHash(const uint64_t& hash, SongId i) -> void;
- auto dbGetHash(const uint64_t& hash) -> std::optional<SongId>;
- auto dbPutSong(SongId id, const std::string& path, const uint64_t& hash)
+ auto dbPutTrackData(const TrackData& s) -> void;
+ auto dbGetTrackData(TrackId id) -> std::optional<TrackData>;
+ auto dbPutHash(const uint64_t& hash, TrackId i) -> void;
+ auto dbGetHash(const uint64_t& hash) -> std::optional<TrackId>;
+ auto dbPutTrack(TrackId id, const std::string& path, const uint64_t& hash)
-> void;
template <typename T>
@@ -128,9 +130,9 @@ class Database {
};
template <>
-auto Database::ParseRecord<Song>(const leveldb::Slice& key,
- const leveldb::Slice& val)
- -> std::optional<Song>;
+auto Database::ParseRecord<Track>(const leveldb::Slice& key,
+ const leveldb::Slice& val)
+ -> std::optional<Track>;
template <>
auto Database::ParseRecord<std::string>(const leveldb::Slice& key,
const leveldb::Slice& val)
diff --git a/src/database/include/env_esp.hpp b/src/database/include/env_esp.hpp
index c7da6d91..eba6e8a9 100644
--- a/src/database/include/env_esp.hpp
+++ b/src/database/include/env_esp.hpp
@@ -18,7 +18,7 @@
namespace leveldb {
-extern std::weak_ptr<tasks::Worker> sBackgroundThread;
+extern std::shared_ptr<tasks::Worker> sBackgroundThread;
// Tracks the files locked by EspEnv::LockFile().
//
diff --git a/src/database/include/records.hpp b/src/database/include/records.hpp
index 1b66ad42..95a1a1e8 100644
--- a/src/database/include/records.hpp
+++ b/src/database/include/records.hpp
@@ -13,7 +13,7 @@
#include "leveldb/db.h"
#include "leveldb/slice.h"
-#include "song.hpp"
+#include "track.hpp"
namespace database {
@@ -31,49 +31,49 @@ class OwningSlice {
};
/*
- * Returns the prefix added to every SongData key. This can be used to iterate
+ * Returns the prefix added to every TrackData key. This can be used to iterate
* over every data record in the database.
*/
auto CreateDataPrefix() -> OwningSlice;
-/* Creates a data key for a song with the specified id. */
-auto CreateDataKey(const SongId& id) -> OwningSlice;
+/* Creates a data key for a track with the specified id. */
+auto CreateDataKey(const TrackId& id) -> OwningSlice;
/*
- * Encodes a SongData instance into bytes, in preparation for storing it within
+ * Encodes a TrackData instance into bytes, in preparation for storing it within
* the database. This encoding is consistent, and will remain stable over time.
*/
-auto CreateDataValue(const SongData& song) -> OwningSlice;
+auto CreateDataValue(const TrackData& track) -> OwningSlice;
/*
- * Parses bytes previously encoded via CreateDataValue back into a SongData. May
- * return nullopt if parsing fails.
+ * Parses bytes previously encoded via CreateDataValue back into a TrackData.
+ * May return nullopt if parsing fails.
*/
-auto ParseDataValue(const leveldb::Slice& slice) -> std::optional<SongData>;
+auto ParseDataValue(const leveldb::Slice& slice) -> std::optional<TrackData>;
/* Creates a hash key for the specified hash. */
auto CreateHashKey(const uint64_t& hash) -> OwningSlice;
/*
- * Encodes a hash value (at this point just a song id) into bytes, in
+ * Encodes a hash value (at this point just a track id) into bytes, in
* preparation for storing within the database. This encoding is consistent, and
* will remain stable over time.
*/
-auto CreateHashValue(SongId id) -> OwningSlice;
+auto CreateHashValue(TrackId id) -> OwningSlice;
/*
- * Parses bytes previously encoded via CreateHashValue back into a song id. May
+ * Parses bytes previously encoded via CreateHashValue back into a track id. May
* return nullopt if parsing fails.
*/
-auto ParseHashValue(const leveldb::Slice&) -> std::optional<SongId>;
+auto ParseHashValue(const leveldb::Slice&) -> std::optional<TrackId>;
-/* Encodes a SongId as bytes. */
-auto SongIdToBytes(SongId id) -> OwningSlice;
+/* Encodes a TrackId as bytes. */
+auto TrackIdToBytes(TrackId id) -> OwningSlice;
/*
- * Converts a song id encoded via SongIdToBytes back into a SongId. May return
- * nullopt if parsing fails.
+ * Converts a track id encoded via TrackIdToBytes back into a TrackId. May
+ * return nullopt if parsing fails.
*/
-auto BytesToSongId(const std::string& bytes) -> std::optional<SongId>;
+auto BytesToTrackId(const std::string& bytes) -> std::optional<TrackId>;
} // namespace database
diff --git a/src/database/include/song.hpp b/src/database/include/song.hpp
deleted file mode 100644
index d03660dc..00000000
--- a/src/database/include/song.hpp
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#pragma once
-
-#include <stdint.h>
-
-#include <optional>
-#include <string>
-#include <utility>
-
-#include "leveldb/db.h"
-#include "span.hpp"
-
-namespace database {
-
-/*
- * Uniquely describes a single song within the database. This value will be
- * consistent across database updates, and should ideally (but is not guaranteed
- * to) endure even across a song being removed and re-added.
- *
- * Four billion songs should be enough for anybody.
- */
-typedef uint32_t SongId;
-
-/*
- * Audio file encodings that we are aware of. Used to select an appropriate
- * decoder at play time.
- *
- * Values of this enum are persisted in this database, so it is probably never a
- * good idea to change the int representation of an existing value.
- */
-enum class Encoding {
- kUnsupported = 0,
- kMp3 = 1,
- kWav = 2,
- kOgg = 3,
- kFlac = 4,
-};
-
-/*
- * Owning container for tag-related song metadata that was extracted from a
- * file.
- */
-struct SongTags {
- Encoding encoding;
- std::optional<std::string> title;
-
- // TODO(jacqueline): It would be nice to use shared_ptr's for the artist and
- // album, since there's likely a fair number of duplicates for each
- // (especially the former).
-
- std::optional<std::string> artist;
- std::optional<std::string> album;
-
- std::optional<int> channels;
- std::optional<int> sample_rate;
- std::optional<int> bits_per_sample;
-
- /*
- * Returns a hash of the 'identifying' tags of this song. That is, a hash that
- * can be used to determine if one song is likely the same as another, across
- * things like re-encoding, re-mastering, or moving the underlying file.
- */
- auto Hash() const -> uint64_t;
-
- bool operator==(const SongTags&) const = default;
-};
-
-/*
- * Immutable owning container for all of the metadata we store for a particular
- * song. This includes two main kinds of metadata:
- * 1. static(ish) attributes, such as the id, path on disk, hash of the tags
- * 2. dynamic attributes, such as the number of times this song has been
- * played.
- *
- * Because a SongData is immutable, it is thread safe but will not reflect any
- * changes to the dynamic attributes that may happen after it was obtained.
- *
- * Songs may be 'tombstoned'; this indicates that the song is no longer present
- * at its previous location on disk, and we do not have any existing files with
- * a matching tags_hash. When this is the case, we ignore this SongData for most
- * purposes. We keep the entry in our database so that we can properly restore
- * dynamic attributes (such as play count) if the song later re-appears on disk.
- */
-class SongData {
- private:
- const SongId id_;
- const std::string filepath_;
- const uint64_t tags_hash_;
- const uint32_t play_count_;
- const bool is_tombstoned_;
-
- public:
- /* Constructor used when adding new songs to the database. */
- SongData(SongId id, const std::string& path, uint64_t hash)
- : id_(id),
- filepath_(path),
- tags_hash_(hash),
- play_count_(0),
- is_tombstoned_(false) {}
-
- SongData(SongId id,
- const std::string& path,
- uint64_t hash,
- uint32_t play_count,
- bool is_tombstoned)
- : id_(id),
- filepath_(path),
- tags_hash_(hash),
- play_count_(play_count),
- is_tombstoned_(is_tombstoned) {}
-
- auto id() const -> SongId { return id_; }
- auto filepath() const -> std::string { return filepath_; }
- auto play_count() const -> uint32_t { return play_count_; }
- auto tags_hash() const -> uint64_t { return tags_hash_; }
- auto is_tombstoned() const -> bool { return is_tombstoned_; }
-
- auto UpdateHash(uint64_t new_hash) const -> SongData;
-
- /*
- * Marks this song data as a 'tombstone'. Tombstoned songs are not playable,
- * and should not generally be shown to users.
- */
- auto Entomb() const -> SongData;
-
- /*
- * Clears the tombstone bit of this song, and updates the path to reflect its
- * new location.
- */
- auto Exhume(const std::string& new_path) const -> SongData;
-
- bool operator==(const SongData&) const = default;
-};
-
-/*
- * Immutable and owning combination of a song's tags and metadata.
- *
- * Note that instances of this class may have a fairly large memory impact, due
- * to the large number of strings they own. Prefer to query the database again
- * (which has its own caching layer), rather than retaining Song instances for a
- * long time.
- */
-class Song {
- public:
- Song(const SongData& data, const SongTags& tags) : data_(data), tags_(tags) {}
- Song(const Song& other) = default;
-
- auto data() const -> const SongData& { return data_; }
- auto tags() const -> const SongTags& { return tags_; }
-
- bool operator==(const Song&) const = default;
- Song operator=(const Song& other) const { return Song(other); }
-
- private:
- const SongData data_;
- const SongTags tags_;
-};
-
-void swap(Song& first, Song& second);
-
-} // namespace database
diff --git a/src/database/include/tag_parser.hpp b/src/database/include/tag_parser.hpp
index 7dab93a1..4be5ad16 100644
--- a/src/database/include/tag_parser.hpp
+++ b/src/database/include/tag_parser.hpp
@@ -8,20 +8,20 @@
#include <string>
-#include "song.hpp"
+#include "track.hpp"
namespace database {
class ITagParser {
public:
virtual ~ITagParser() {}
- virtual auto ReadAndParseTags(const std::string& path, SongTags* out)
+ virtual auto ReadAndParseTags(const std::string& path, TrackTags* out)
-> bool = 0;
};
class TagParserImpl : public ITagParser {
public:
- virtual auto ReadAndParseTags(const std::string& path, SongTags* out)
+ virtual auto ReadAndParseTags(const std::string& path, TrackTags* out)
-> bool override;
};
diff --git a/src/database/include/track.hpp b/src/database/include/track.hpp
new file mode 100644
index 00000000..5a0c0ca8
--- /dev/null
+++ b/src/database/include/track.hpp
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2023 jacqueline <me@jacqueline.id.au>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <optional>
+#include <string>
+#include <utility>
+
+#include "leveldb/db.h"
+#include "span.hpp"
+
+namespace database {
+
+/*
+ * Uniquely describes a single track within the database. This value will be
+ * consistent across database updates, and should ideally (but is not guaranteed
+ * to) endure even across a track being removed and re-added.
+ *
+ * Four billion tracks should be enough for anybody.
+ */
+typedef uint32_t TrackId;
+
+/*
+ * Audio file encodings that we are aware of. Used to select an appropriate
+ * decoder at play time.
+ *
+ * Values of this enum are persisted in this database, so it is probably never a
+ * good idea to change the int representation of an existing value.
+ */
+enum class Encoding {
+ kUnsupported = 0,
+ kMp3 = 1,
+ kWav = 2,
+ kOgg = 3,
+ kFlac = 4,
+};
+
+/*
+ * Owning container for tag-related track metadata that was extracted from a
+ * file.
+ */
+struct TrackTags {
+ Encoding encoding;
+ std::optional<std::string> title;
+
+ // TODO(jacqueline): It would be nice to use shared_ptr's for the artist and
+ // album, since there's likely a fair number of duplicates for each
+ // (especially the former).
+
+ std::optional<std::string> artist;
+ std::optional<std::string> album;
+
+ std::optional<int> channels;
+ std::optional<int> sample_rate;
+ std::optional<int> bits_per_sample;
+
+ /*
+ * Returns a hash of the 'identifying' tags of this track. That is, a hash
+ * that can be used to determine if one track is likely the same as another,
+ * across things like re-encoding, re-mastering, or moving the underlying
+ * file.
+ */
+ auto Hash() const -> uint64_t;
+
+ bool operator==(const TrackTags&) const = default;
+};
+
+/*
+ * Immutable owning container for all of the metadata we store for a particular
+ * track. This includes two main kinds of metadata:
+ * 1. static(ish) attributes, such as the id, path on disk, hash of the tags
+ * 2. dynamic attributes, such as the number of times this track has been
+ * played.
+ *
+ * Because a TrackData is immutable, it is thread safe but will not reflect any
+ * changes to the dynamic attributes that may happen after it was obtained.
+ *
+ * Tracks may be 'tombstoned'; this indicates that the track is no longer
+ * present at its previous location on disk, and we do not have any existing
+ * files with a matching tags_hash. When this is the case, we ignore this
+ * TrackData for most purposes. We keep the entry in our database so that we can
+ * properly restore dynamic attributes (such as play count) if the track later
+ * re-appears on disk.
+ */
+class TrackData {
+ private:
+ const TrackId id_;
+ const std::string filepath_;
+ const uint64_t tags_hash_;
+ const uint32_t play_count_;
+ const bool is_tombstoned_;
+
+ public:
+ /* Constructor used when adding new tracks to the database. */
+ TrackData(TrackId id, const std::string& path, uint64_t hash)
+ : id_(id),
+ filepath_(path),
+ tags_hash_(hash),
+ play_count_(0),
+ is_tombstoned_(false) {}
+
+ TrackData(TrackId id,
+ const std::string& path,
+ uint64_t hash,
+ uint32_t play_count,
+ bool is_tombstoned)
+ : id_(id),
+ filepath_(path),
+ tags_hash_(hash),
+ play_count_(play_count),
+ is_tombstoned_(is_tombstoned) {}
+
+ auto id() const -> TrackId { return id_; }
+ auto filepath() const -> std::string { return filepath_; }
+ auto play_count() const -> uint32_t { return play_count_; }
+ auto tags_hash() const -> uint64_t { return tags_hash_; }
+ auto is_tombstoned() const -> bool { return is_tombstoned_; }
+
+ auto UpdateHash(uint64_t new_hash) const -> TrackData;
+
+ /*
+ * Marks this track data as a 'tombstone'. Tombstoned tracks are not playable,
+ * and should not generally be shown to users.
+ */
+ auto Entomb() const -> TrackData;
+
+ /*
+ * Clears the tombstone bit of this track, and updates the path to reflect its
+ * new location.
+ */
+ auto Exhume(const std::string& new_path) const -> TrackData;
+
+ bool operator==(const TrackData&) const = default;
+};
+
+/*
+ * Immutable and owning combination of a track's tags and metadata.
+ *
+ * Note that instances of this class may have a fairly large memory impact, due
+ * to the large number of strings they own. Prefer to query the database again
+ * (which has its own caching layer), rather than retaining Track instances for
+ * a long time.
+ */
+class Track {
+ public:
+ Track(const TrackData& data, const TrackTags& tags)
+ : data_(data), tags_(tags) {}
+ Track(const Track& other) = default;
+
+ auto data() const -> const TrackData& { return data_; }
+ auto tags() const -> const TrackTags& { return tags_; }
+
+ bool operator==(const Track&) const = default;
+ Track operator=(const Track& other) const { return Track(other); }
+
+ private:
+ const TrackData data_;
+ const TrackTags tags_;
+};
+
+void swap(Track& first, Track& second);
+
+} // namespace database
diff --git a/src/database/records.cpp b/src/database/records.cpp
index f04e5da7..49e5db0b 100644
--- a/src/database/records.cpp
+++ b/src/database/records.cpp
@@ -14,7 +14,7 @@
#include "cbor.h"
#include "esp_log.h"
-#include "song.hpp"
+#include "track.hpp"
namespace database {
@@ -60,14 +60,14 @@ auto CreateDataPrefix() -> OwningSlice {
return OwningSlice({data, 2});
}
-auto CreateDataKey(const SongId& id) -> OwningSlice {
+auto CreateDataKey(const TrackId& id) -> OwningSlice {
std::ostringstream output;
output.put(kDataPrefix).put(kFieldSeparator);
- output << SongIdToBytes(id).data;
+ output << TrackIdToBytes(id).data;
return OwningSlice(output.str());
}
-auto CreateDataValue(const SongData& song) -> OwningSlice {
+auto CreateDataValue(const TrackData& track) -> OwningSlice {
uint8_t* buf;
std::size_t buf_len = cbor_encode(&buf, [&](CborEncoder* enc) {
CborEncoder array_encoder;
@@ -77,28 +77,28 @@ auto CreateDataValue(const SongData& song) -> OwningSlice {
ESP_LOGE(kTag, "encoding err %u", err);
return;
}
- err = cbor_encode_int(&array_encoder, song.id());
+ err = cbor_encode_int(&array_encoder, track.id());
if (err != CborNoError && err != CborErrorOutOfMemory) {
ESP_LOGE(kTag, "encoding err %u", err);
return;
}
- err = cbor_encode_text_string(&array_encoder, song.filepath().c_str(),
- song.filepath().size());
+ err = cbor_encode_text_string(&array_encoder, track.filepath().c_str(),
+ track.filepath().size());
if (err != CborNoError && err != CborErrorOutOfMemory) {
ESP_LOGE(kTag, "encoding err %u", err);
return;
}
- err = cbor_encode_uint(&array_encoder, song.tags_hash());
+ err = cbor_encode_uint(&array_encoder, track.tags_hash());
if (err != CborNoError && err != CborErrorOutOfMemory) {
ESP_LOGE(kTag, "encoding err %u", err);
return;
}
- err = cbor_encode_int(&array_encoder, song.play_count());
+ err = cbor_encode_int(&array_encoder, track.play_count());
if (err != CborNoError && err != CborErrorOutOfMemory) {
ESP_LOGE(kTag, "encoding err %u", err);
return;
}
- err = cbor_encode_boolean(&array_encoder, song.is_tombstoned());
+ err = cbor_encode_boolean(&array_encoder, track.is_tombstoned());
if (err != CborNoError && err != CborErrorOutOfMemory) {
ESP_LOGE(kTag, "encoding err %u", err);
return;
@@ -114,7 +114,7 @@ auto CreateDataValue(const SongData& song) -> OwningSlice {
return OwningSlice(as_str);
}
-auto ParseDataValue(const leveldb::Slice& slice) -> std::optional<SongData> {
+auto ParseDataValue(const leveldb::Slice& slice) -> std::optional<TrackData> {
CborParser parser;
CborValue container;
CborError err;
@@ -135,7 +135,7 @@ auto ParseDataValue(const leveldb::Slice& slice) -> std::optional<SongData> {
if (err != CborNoError) {
return {};
}
- SongId id = raw_int;
+ TrackId id = raw_int;
err = cbor_value_advance(&val);
if (err != CborNoError || !cbor_value_is_text_string(&val)) {
return {};
@@ -176,7 +176,7 @@ auto ParseDataValue(const leveldb::Slice& slice) -> std::optional<SongData> {
return {};
}
- return SongData(id, path, hash, play_count, is_tombstoned);
+ return TrackData(id, path, hash, play_count, is_tombstoned);
}
auto CreateHashKey(const uint64_t& hash) -> OwningSlice {
@@ -193,15 +193,15 @@ auto CreateHashKey(const uint64_t& hash) -> OwningSlice {
return OwningSlice(output.str());
}
-auto ParseHashValue(const leveldb::Slice& slice) -> std::optional<SongId> {
- return BytesToSongId(slice.ToString());
+auto ParseHashValue(const leveldb::Slice& slice) -> std::optional<TrackId> {
+ return BytesToTrackId(slice.ToString());
}
-auto CreateHashValue(SongId id) -> OwningSlice {
- return SongIdToBytes(id);
+auto CreateHashValue(TrackId id) -> OwningSlice {
+ return TrackIdToBytes(id);
}
-auto SongIdToBytes(SongId id) -> OwningSlice {
+auto TrackIdToBytes(TrackId id) -> OwningSlice {
uint8_t buf[8];
CborEncoder enc;
cbor_encoder_init(&enc, buf, sizeof(buf), 0);
@@ -211,7 +211,7 @@ auto SongIdToBytes(SongId id) -> OwningSlice {
return OwningSlice(as_str);
}
-auto BytesToSongId(const std::string& bytes) -> std::optional<SongId> {
+auto BytesToTrackId(const std::string& bytes) -> std::optional<TrackId> {
CborParser parser;
CborValue val;
cbor_parser_init(reinterpret_cast<const uint8_t*>(bytes.data()), bytes.size(),
diff --git a/src/database/tag_parser.cpp b/src/database/tag_parser.cpp
index 27d4163f..83b0a796 100644
--- a/src/database/tag_parser.cpp
+++ b/src/database/tag_parser.cpp
@@ -17,7 +17,7 @@ namespace libtags {
struct Aux {
FIL file;
FILINFO info;
- SongTags* tags;
+ TrackTags* tags;
};
static int read(Tagctx* ctx, void* buf, int cnt) {
@@ -71,8 +71,14 @@ static void toc(Tagctx* ctx, int ms, int offset) {}
static const std::size_t kBufSize = 1024;
static const char* kTag = "TAGS";
-auto TagParserImpl::ReadAndParseTags(const std::string& path, SongTags* out)
+auto TagParserImpl::ReadAndParseTags(const std::string& path, TrackTags* out)
-> bool {
+ if (path.ends_with(".m4a")) {
+ // TODO(jacqueline): Re-enabled once libtags is fixed.
+ ESP_LOGW(kTag, "skipping m4a %s", path.c_str());
+ return false;
+ }
+
libtags::Aux aux;
aux.tags = out;
if (f_stat(path.c_str(), &aux.info) != FR_OK ||
@@ -96,6 +102,7 @@ auto TagParserImpl::ReadAndParseTags(const std::string& path, SongTags* out)
if (res != 0) {
// Parsing failed.
+ ESP_LOGE(kTag, "tag parsing failed, reason %d", res);
return false;
}
@@ -103,6 +110,15 @@ auto TagParserImpl::ReadAndParseTags(const std::string& path, SongTags* out)
case Fmp3:
out->encoding = Encoding::kMp3;
break;
+ case Fogg:
+ out->encoding = Encoding::kOgg;
+ break;
+ case Fflac:
+ out->encoding = Encoding::kFlac;
+ break;
+ case Fwav:
+ out->encoding = Encoding::kWav;
+ break;
default:
out->encoding = Encoding::kUnsupported;
}
diff --git a/src/database/test/test_database.cpp b/src/database/test/test_database.cpp
index ebaa6307..1ce364d9 100644
--- a/src/database/test/test_database.cpp
+++ b/src/database/test/test_database.cpp
@@ -18,41 +18,41 @@
#include "file_gatherer.hpp"
#include "i2c_fixture.hpp"
#include "leveldb/db.h"
-#include "song.hpp"
#include "spi_fixture.hpp"
#include "tag_parser.hpp"
+#include "track.hpp"
namespace database {
class TestBackends : public IFileGatherer, public ITagParser {
public:
- std::map<std::string, SongTags> songs;
+ std::map<std::string, TrackTags> tracks;
- auto MakeSong(const std::string& path, const std::string& title) -> void {
- SongTags tags;
+ auto MakeTrack(const std::string& path, const std::string& title) -> void {
+ TrackTags tags;
tags.encoding = Encoding::kMp3;
tags.title = title;
- songs[path] = tags;
+ tracks[path] = tags;
}
auto FindFiles(const std::string& root,
std::function<void(const std::string&)> cb) -> void override {
- for (auto keyval : songs) {
+ for (auto keyval : tracks) {
std::invoke(cb, keyval.first);
}
}
- auto ReadAndParseTags(const std::string& path, SongTags* out)
+ auto ReadAndParseTags(const std::string& path, TrackTags* out)
-> bool override {
- if (songs.contains(path)) {
- *out = songs.at(path);
+ if (tracks.contains(path)) {
+ *out = tracks.at(path);
return true;
}
return false;
}
};
-TEST_CASE("song database", "[integration]") {
+TEST_CASE("track database", "[integration]") {
I2CFixture i2c;
SpiFixture spi;
drivers::DriverCache drivers;
@@ -60,104 +60,104 @@ TEST_CASE("song database", "[integration]") {
Database::Destroy();
- TestBackends songs;
- auto open_res = Database::Open(&songs, &songs);
+ TestBackends tracks;
+ auto open_res = Database::Open(&tracks, &tracks);
REQUIRE(open_res.has_value());
std::unique_ptr<Database> db(open_res.value());
SECTION("empty database") {
- std::unique_ptr<Result<Song>> res(db->GetSongs(10).get());
+ std::unique_ptr<Result<Track>> res(db->GetTracks(10).get());
REQUIRE(res->values().size() == 0);
}
- SECTION("add new songs") {
- songs.MakeSong("song1.mp3", "Song 1");
- songs.MakeSong("song2.wav", "Song 2");
- songs.MakeSong("song3.exe", "Song 3");
+ SECTION("add new tracks") {
+ tracks.MakeTrack("track1.mp3", "Track 1");
+ tracks.MakeTrack("track2.wav", "Track 2");
+ tracks.MakeTrack("track3.exe", "Track 3");
db->Update();
- std::unique_ptr<Result<Song>> res(db->GetSongs(10).get());
+ std::unique_ptr<Result<Track>> res(db->GetTracks(10).get());
REQUIRE(res->values().size() == 3);
- CHECK(*res->values().at(0).tags().title == "Song 1");
+ CHECK(*res->values().at(0).tags().title == "Track 1");
CHECK(res->values().at(0).data().id() == 1);
- CHECK(*res->values().at(1).tags().title == "Song 2");
+ CHECK(*res->values().at(1).tags().title == "Track 2");
CHECK(res->values().at(1).data().id() == 2);
- CHECK(*res->values().at(2).tags().title == "Song 3");
+ CHECK(*res->values().at(2).tags().title == "Track 3");
CHECK(res->values().at(2).data().id() == 3);
SECTION("update with no filesystem changes") {
db->Update();
- std::unique_ptr<Result<Song>> new_res(db->GetSongs(10).get());
+ std::unique_ptr<Result<Track>> new_res(db->GetTracks(10).get());
REQUIRE(new_res->values().size() == 3);
CHECK(res->values().at(0) == new_res->values().at(0));
CHECK(res->values().at(1) == new_res->values().at(1));
CHECK(res->values().at(2) == new_res->values().at(2));
}
- SECTION("update with all songs gone") {
- songs.songs.clear();
+ SECTION("update with all tracks gone") {
+ tracks.tracks.clear();
db->Update();
- std::unique_ptr<Result<Song>> new_res(db->GetSongs(10).get());
+ std::unique_ptr<Result<Track>> new_res(db->GetTracks(10).get());
CHECK(new_res->values().size() == 0);
- SECTION("update with one song returned") {
- songs.MakeSong("song2.wav", "Song 2");
+ SECTION("update with one track returned") {
+ tracks.MakeTrack("track2.wav", "Track 2");
db->Update();
- std::unique_ptr<Result<Song>> new_res(db->GetSongs(10).get());
+ std::unique_ptr<Result<Track>> new_res(db->GetTracks(10).get());
REQUIRE(new_res->values().size() == 1);
CHECK(res->values().at(1) == new_res->values().at(0));
}
}
- SECTION("update with one song gone") {
- songs.songs.erase("song2.wav");
+ SECTION("update with one track gone") {
+ tracks.tracks.erase("track2.wav");
db->Update();
- std::unique_ptr<Result<Song>> new_res(db->GetSongs(10).get());
+ std::unique_ptr<Result<Track>> new_res(db->GetTracks(10).get());
REQUIRE(new_res->values().size() == 2);
CHECK(res->values().at(0) == new_res->values().at(0));
CHECK(res->values().at(2) == new_res->values().at(1));
}
SECTION("update with tags changed") {
- songs.MakeSong("song3.exe", "The Song 3");
+ tracks.MakeTrack("track3.exe", "The Track 3");
db->Update();
- std::unique_ptr<Result<Song>> new_res(db->GetSongs(10).get());
+ std::unique_ptr<Result<Track>> new_res(db->GetTracks(10).get());
REQUIRE(new_res->values().size() == 3);
CHECK(res->values().at(0) == new_res->values().at(0));
CHECK(res->values().at(1) == new_res->values().at(1));
- CHECK(*new_res->values().at(2).tags().title == "The Song 3");
+ CHECK(*new_res->values().at(2).tags().title == "The Track 3");
// The id should not have changed, since this was just a tag update.
CHECK(res->values().at(2).data().id() ==
new_res->values().at(2).data().id());
}
- SECTION("update with one new song") {
- songs.MakeSong("my song.midi", "Song 1 (nightcore remix)");
+ SECTION("update with one new track") {
+ tracks.MakeTrack("my track.midi", "Track 1 (nightcore remix)");
db->Update();
- std::unique_ptr<Result<Song>> new_res(db->GetSongs(10).get());
+ std::unique_ptr<Result<Track>> new_res(db->GetTracks(10).get());
REQUIRE(new_res->values().size() == 4);
CHECK(res->values().at(0) == new_res->values().at(0));
CHECK(res->values().at(1) == new_res->values().at(1));
CHECK(res->values().at(2) == new_res->values().at(2));
CHECK(*new_res->values().at(3).tags().title ==
- "Song 1 (nightcore remix)");
+ "Track 1 (nightcore remix)");
CHECK(new_res->values().at(3).data().id() == 4);
}
- SECTION("get songs with pagination") {
- std::unique_ptr<Result<Song>> res(db->GetSongs(1).get());
+ SECTION("get tracks with pagination") {
+ std::unique_ptr<Result<Track>> res(db->GetTracks(1).get());
REQUIRE(res->values().size() == 1);
CHECK(res->values().at(0).data().id() == 1);
diff --git a/src/database/test/test_records.cpp b/src/database/test/test_records.cpp
index ca518458..5729003e 100644
--- a/src/database/test/test_records.cpp
+++ b/src/database/test/test_records.cpp
@@ -25,9 +25,9 @@ std::string ToHex(const std::string& s) {
namespace database {
TEST_CASE("database record encoding", "[unit]") {
- SECTION("song id to bytes") {
- SongId id = 1234678;
- OwningSlice as_bytes = SongIdToBytes(id);
+ SECTION("track id to bytes") {
+ TrackId id = 1234678;
+ OwningSlice as_bytes = TrackIdToBytes(id);
SECTION("encodes correctly") {
// Purposefully a brittle test, since we need to be very careful about
@@ -44,18 +44,18 @@ TEST_CASE("database record encoding", "[unit]") {
}
SECTION("round-trips") {
- CHECK(*BytesToSongId(as_bytes.data) == id);
+ CHECK(*BytesToTrackId(as_bytes.data) == id);
}
SECTION("encodes compactly") {
- OwningSlice small_id = SongIdToBytes(1);
- OwningSlice large_id = SongIdToBytes(999999);
+ OwningSlice small_id = TrackIdToBytes(1);
+ OwningSlice large_id = TrackIdToBytes(999999);
CHECK(small_id.data.size() < large_id.data.size());
}
SECTION("decoding rejects garbage") {
- std::optional<SongId> res = BytesToSongId("i'm gay");
+ std::optional<TrackId> res = BytesToTrackId("i'm gay");
CHECK(res.has_value() == false);
}
@@ -73,7 +73,7 @@ TEST_CASE("database record encoding", "[unit]") {
}
SECTION("data values") {
- SongData data(123, "/some/path.mp3", 0xACAB, 69, true);
+ TrackData data(123, "/some/path.mp3", 0xACAB, 69, true);
OwningSlice enc = CreateDataValue(data);
@@ -109,7 +109,7 @@ TEST_CASE("database record encoding", "[unit]") {
}
SECTION("decoding rejects garbage") {
- std::optional<SongData> res = ParseDataValue("hi!");
+ std::optional<TrackData> res = ParseDataValue("hi!");
CHECK(res.has_value() == false);
}
@@ -129,14 +129,14 @@ TEST_CASE("database record encoding", "[unit]") {
SECTION("hash values") {
OwningSlice val = CreateHashValue(123456);
- CHECK(val.data == SongIdToBytes(123456).data);
+ CHECK(val.data == TrackIdToBytes(123456).data);
SECTION("round-trips") {
CHECK(ParseHashValue(val.slice) == 123456);
}
SECTION("decoding rejects garbage") {
- std::optional<SongId> res = ParseHashValue("the first song :)");
+ std::optional<TrackId> res = ParseHashValue("the first track :)");
CHECK(res.has_value() == false);
}
diff --git a/src/database/song.cpp b/src/database/track.cpp
index c717e55e..00acc1f6 100644
--- a/src/database/song.cpp
+++ b/src/database/track.cpp
@@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-only
*/
-#include "song.hpp"
+#include "track.hpp"
#include <komihash.h>
@@ -19,8 +19,8 @@ auto HashString(komihash_stream_t* stream, std::string str) -> void {
* Uses a komihash stream to incrementally hash tags. This lowers the function's
* memory footprint a little so that it's safe to call from any stack.
*/
-auto SongTags::Hash() const -> uint64_t {
- // TODO(jacqueline): this function doesn't work very well for songs with no
+auto TrackTags::Hash() const -> uint64_t {
+ // TODO(jacqueline): this function doesn't work very well for tracks with no
// tags at all.
komihash_stream_t stream;
komihash_stream_init(&stream, 0);
@@ -30,20 +30,20 @@ auto SongTags::Hash() const -> uint64_t {
return komihash_stream_final(&stream);
}
-auto SongData::UpdateHash(uint64_t new_hash) const -> SongData {
- return SongData(id_, filepath_, new_hash, play_count_, is_tombstoned_);
+auto TrackData::UpdateHash(uint64_t new_hash) const -> TrackData {
+ return TrackData(id_, filepath_, new_hash, play_count_, is_tombstoned_);
}
-auto SongData::Entomb() const -> SongData {
- return SongData(id_, filepath_, tags_hash_, play_count_, true);
+auto TrackData::Entomb() const -> TrackData {
+ return TrackData(id_, filepath_, tags_hash_, play_count_, true);
}
-auto SongData::Exhume(const std::string& new_path) const -> SongData {
- return SongData(id_, new_path, tags_hash_, play_count_, false);
+auto TrackData::Exhume(const std::string& new_path) const -> TrackData {
+ return TrackData(id_, new_path, tags_hash_, play_count_, false);
}
-void swap(Song& first, Song& second) {
- Song temp = first;
+void swap(Track& first, Track& second) {
+ Track temp = first;
first = second;
second = temp;
}