From c6bb42cdd21b63accd20012373a8a0e41d8566f5 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Thu, 15 Jun 2023 10:42:28 +1000 Subject: [PATCH] song -> track --- src/app_console/app_console.cpp | 19 +-- src/audio/audio_fsm.cpp | 8 +- src/audio/audio_task.cpp | 4 +- src/audio/fatfs_audio_input.cpp | 4 +- src/audio/include/audio_events.hpp | 10 +- src/audio/include/audio_fsm.hpp | 10 +- src/audio/include/fatfs_audio_input.hpp | 2 +- src/database/CMakeLists.txt | 2 +- src/database/database.cpp | 130 +++++++++--------- src/database/include/database.hpp | 24 ++-- src/database/include/records.hpp | 36 ++--- src/database/include/song.hpp | 166 ----------------------- src/database/include/tag_parser.hpp | 6 +- src/database/include/track.hpp | 169 ++++++++++++++++++++++++ src/database/records.cpp | 38 +++--- src/database/tag_parser.cpp | 4 +- src/database/test/test_database.cpp | 80 +++++------ src/database/test/test_records.cpp | 22 +-- src/database/{song.cpp => track.cpp} | 22 +-- 19 files changed, 380 insertions(+), 376 deletions(-) delete mode 100644 src/database/include/song.hpp create mode 100644 src/database/include/track.hpp rename src/database/{song.cpp => track.cpp} (62%) diff --git a/src/app_console/app_console.cpp b/src/app_console/app_console.cpp index 0483bde9..457d66f6 100644 --- a/src/app_console/app_console.cpp +++ b/src/app_console/app_console.cpp @@ -121,8 +121,8 @@ void RegisterDbInit() { esp_console_cmd_register(&cmd); } -int CmdDbSongs(int argc, char** argv) { - static const std::string usage = "usage: db_songs"; +int CmdDbTracks(int argc, char** argv) { + static const std::string usage = "usage: db_tracks"; if (argc != 1) { std::cout << usage << std::endl; return 1; @@ -133,9 +133,10 @@ int CmdDbSongs(int argc, char** argv) { std::cout << "no database open" << std::endl; return 1; } - std::unique_ptr> res(db->GetSongs(5).get()); + std::unique_ptr> res( + db->GetTracks(5).get()); while (true) { - for (database::Song s : res->values()) { + for (database::Track s : res->values()) { std::cout << s.tags().title.value_or("[BLANK]") << std::endl; } if (res->next_page()) { @@ -149,11 +150,11 @@ int CmdDbSongs(int argc, char** argv) { return 0; } -void RegisterDbSongs() { - esp_console_cmd_t cmd{.command = "db_songs", - .help = "lists titles of ALL songs in the database", +void RegisterDbTracks() { + esp_console_cmd_t cmd{.command = "db_tracks", + .help = "lists titles of ALL tracks in the database", .hint = NULL, - .func = &CmdDbSongs, + .func = &CmdDbTracks, .argtable = NULL}; esp_console_cmd_register(&cmd); } @@ -217,7 +218,7 @@ auto AppConsole::RegisterExtraComponents() -> void { RegisterAudioStatus(); */ RegisterDbInit(); - RegisterDbSongs(); + RegisterDbTracks(); RegisterDbDump(); } diff --git a/src/audio/audio_fsm.cpp b/src/audio/audio_fsm.cpp index 6c974905..0be28250 100644 --- a/src/audio/audio_fsm.cpp +++ b/src/audio/audio_fsm.cpp @@ -28,7 +28,7 @@ std::unique_ptr AudioState::sFileSource; std::unique_ptr AudioState::sI2SOutput; std::vector> AudioState::sPipeline; -std::deque AudioState::sSongQueue; +std::deque AudioState::sTrackQueue; auto AudioState::Init(drivers::GpioExpander* gpio_expander, std::weak_ptr database) -> bool { @@ -83,11 +83,11 @@ void Playback::exit() { void Playback::react(const InputFileFinished& ev) { ESP_LOGI(kTag, "finished file"); - if (sSongQueue.empty()) { + if (sTrackQueue.empty()) { return; } - EnqueuedItem next_item = sSongQueue.front(); - sSongQueue.pop_front(); + EnqueuedItem next_item = sTrackQueue.front(); + sTrackQueue.pop_front(); if (std::holds_alternative(next_item)) { sFileSource->OpenFile(std::get(next_item)); diff --git a/src/audio/audio_task.cpp b/src/audio/audio_task.cpp index eea84e45..394a55b6 100644 --- a/src/audio/audio_task.cpp +++ b/src/audio/audio_task.cpp @@ -45,7 +45,7 @@ namespace task { static const char* kTag = "task"; // The default amount of time to wait between pipeline iterations for a single -// song. +// track. static constexpr uint_fast16_t kDefaultDelayTicks = pdMS_TO_TICKS(5); static constexpr uint_fast16_t kMaxDelayTicks = pdMS_TO_TICKS(10); static constexpr uint_fast16_t kMinDelayTicks = pdMS_TO_TICKS(1); @@ -54,7 +54,7 @@ void AudioTaskMain(std::unique_ptr pipeline, IAudioSink* sink) { // The stream format for bytes currently in the sink buffer. std::optional output_format; - // How long to wait between pipeline iterations. This is reset for each song, + // How long to wait between pipeline iterations. This is reset for each track, // and readjusted on the fly to maintain a reasonable amount playback buffer. // Buffering too much will mean we process samples inefficiently, wasting CPU // time, whilst buffering too little will affect the quality of the output. diff --git a/src/audio/fatfs_audio_input.cpp b/src/audio/fatfs_audio_input.cpp index eaa62ee3..c26ff0ad 100644 --- a/src/audio/fatfs_audio_input.cpp +++ b/src/audio/fatfs_audio_input.cpp @@ -24,12 +24,12 @@ #include "audio_element.hpp" #include "chunk.hpp" -#include "song.hpp" #include "stream_buffer.hpp" #include "stream_event.hpp" #include "stream_info.hpp" #include "stream_message.hpp" #include "tag_parser.hpp" +#include "track.hpp" #include "types.hpp" static const char* kTag = "SRC"; @@ -53,7 +53,7 @@ auto FatfsAudioInput::OpenFile(const std::string& path) -> bool { ESP_LOGI(kTag, "opening file %s", path.c_str()); database::TagParserImpl tag_parser; - database::SongTags tags; + database::TrackTags tags; if (!tag_parser.ReadAndParseTags(path, &tags)) { ESP_LOGE(kTag, "failed to read tags"); tags.encoding = database::Encoding::kFlac; diff --git a/src/audio/include/audio_events.hpp b/src/audio/include/audio_events.hpp index 920b134e..eebf5efe 100644 --- a/src/audio/include/audio_events.hpp +++ b/src/audio/include/audio_events.hpp @@ -10,7 +10,7 @@ #include "tinyfsm.hpp" -#include "song.hpp" +#include "track.hpp" namespace audio { @@ -18,10 +18,10 @@ struct PlayFile : tinyfsm::Event { std::string filename; }; -struct PlaySong : tinyfsm::Event { - database::SongId id; - std::optional data; - std::optional tags; +struct PlayTrack : tinyfsm::Event { + database::TrackId id; + std::optional data; + std::optional tags; }; struct InputFileFinished : tinyfsm::Event {}; diff --git a/src/audio/include/audio_fsm.hpp b/src/audio/include/audio_fsm.hpp index 5dad87c0..72654ab5 100644 --- a/src/audio/include/audio_fsm.hpp +++ b/src/audio/include/audio_fsm.hpp @@ -17,9 +17,9 @@ #include "gpio_expander.hpp" #include "i2s_audio_output.hpp" #include "i2s_dac.hpp" -#include "song.hpp" #include "storage.hpp" #include "tinyfsm.hpp" +#include "track.hpp" #include "system_events.hpp" @@ -39,7 +39,7 @@ class AudioState : public tinyfsm::Fsm { void react(const tinyfsm::Event& ev) {} virtual void react(const system_fsm::BootComplete&) {} - virtual void react(const PlaySong&) {} + virtual void react(const PlayTrack&) {} virtual void react(const PlayFile&) {} virtual void react(const InputFileFinished&) {} @@ -55,8 +55,8 @@ class AudioState : public tinyfsm::Fsm { static std::unique_ptr sI2SOutput; static std::vector> sPipeline; - typedef std::variant EnqueuedItem; - static std::deque sSongQueue; + typedef std::variant EnqueuedItem; + static std::deque sTrackQueue; }; namespace states { @@ -69,7 +69,7 @@ class Uninitialised : public AudioState { class Standby : public AudioState { public: - void react(const PlaySong&) override {} + void react(const PlayTrack&) override {} void react(const PlayFile&) override; using AudioState::react; }; diff --git a/src/audio/include/fatfs_audio_input.hpp b/src/audio/include/fatfs_audio_input.hpp index 1f9f36a1..f5a65d0d 100644 --- a/src/audio/include/fatfs_audio_input.hpp +++ b/src/audio/include/fatfs_audio_input.hpp @@ -18,8 +18,8 @@ #include "ff.h" #include "freertos/message_buffer.h" #include "freertos/queue.h" -#include "song.hpp" #include "span.hpp" +#include "track.hpp" #include "audio_element.hpp" #include "stream_buffer.hpp" 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..9206256f 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -28,16 +28,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 sEnv; static const char* kTag = "DB"; -static const char kSongIdKey[] = "next_song_id"; +static const char kTrackIdKey[] = "next_track_id"; static std::atomic sIsDbOpen(false); @@ -128,8 +128,8 @@ Database::~Database() { auto Database::Update() -> std::future { return worker_task_->Dispatch([&]() -> 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 +138,8 @@ auto Database::Update() -> std::future { OwningSlice prefix = CreateDataPrefix(); it->Seek(prefix.slice); while (it->Valid() && it->key().starts_with(prefix.slice)) { - std::optional song = ParseDataValue(it->value()); - if (!song) { + std::optional 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 +147,33 @@ auto Database::Update() -> std::future { 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 +182,9 @@ auto Database::Update() -> std::future { 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 +194,32 @@ auto Database::Update() -> std::future { // Check for any existing record with the same hash. uint64_t hash = tags.Hash(); OwningSlice key = CreateHashKey(hash); - std::optional existing_hash; + std::optional 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 existing_data = dbGetSongData(*existing_hash); + std::optional 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 +227,14 @@ auto Database::Update() -> std::future { }); } -auto Database::GetSongs(std::size_t page_size) -> std::future*> { - return worker_task_->Dispatch*>([=, this]() -> Result* { - Continuation c{.iterator = nullptr, - .prefix = CreateDataPrefix().data, - .start_key = CreateDataPrefix().data, - .forward = true, - .was_prev_forward = true, - .page_size = page_size}; +auto Database::GetTracks(std::size_t page_size) -> std::future*> { + return worker_task_->Dispatch*>([=, this]() -> Result* { + Continuation 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 +260,32 @@ auto Database::GetPage(Continuation* c) -> std::future*> { [=, this]() -> Result* { return dbGetPage(copy); }); } -template auto Database::GetPage(Continuation* c) - -> std::future*>; +template auto Database::GetPage(Continuation* c) + -> std::future*>; template auto Database::GetPage(Continuation* c) -> std::future*>; -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 +293,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 +301,7 @@ auto Database::dbPutSongData(const SongData& s) -> void { } } -auto Database::dbGetSongData(SongId id) -> std::optional { +auto Database::dbGetTrackData(TrackId id) -> std::optional { OwningSlice key = CreateDataKey(id); std::string raw_val; if (!db_->Get(leveldb::ReadOptions(), key.slice, &raw_val).ok()) { @@ -311,7 +311,7 @@ auto Database::dbGetSongData(SongId id) -> std::optional { 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 +319,7 @@ auto Database::dbPutHash(const uint64_t& hash, SongId i) -> void { } } -auto Database::dbGetHash(const uint64_t& hash) -> std::optional { +auto Database::dbGetHash(const uint64_t& hash) -> std::optional { OwningSlice key = CreateHashKey(hash); std::string raw_val; if (!db_->Get(leveldb::ReadOptions(), key.slice, &raw_val).ok()) { @@ -329,10 +329,10 @@ auto Database::dbGetHash(const uint64_t& hash) -> std::optional { 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 +455,24 @@ auto Database::dbGetPage(const Continuation& c) -> Result* { return new Result(std::move(records), next_page, prev_page); } -template auto Database::dbGetPage(const Continuation& c) - -> Result*; +template auto Database::dbGetPage(const Continuation& c) + -> Result*; template auto Database::dbGetPage( const Continuation& c) -> Result*; template <> -auto Database::ParseRecord(const leveldb::Slice& key, - const leveldb::Slice& val) - -> std::optional { - std::optional data = ParseDataValue(val); +auto Database::ParseRecord(const leveldb::Slice& key, + const leveldb::Slice& val) + -> std::optional { + std::optional 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/include/database.hpp b/src/database/include/database.hpp index 5214b8df..1a8388e8 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,7 @@ class Database { auto Update() -> std::future; - auto GetSongs(std::size_t page_size) -> std::future*>; + auto GetTracks(std::size_t page_size) -> std::future*>; auto GetDump(std::size_t page_size) -> std::future*>; template @@ -109,14 +109,14 @@ class Database { ITagParser* tag_parser, std::shared_ptr 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; - auto dbPutHash(const uint64_t& hash, SongId i) -> void; - auto dbGetHash(const uint64_t& hash) -> std::optional; - auto dbPutSong(SongId id, const std::string& path, const uint64_t& hash) + auto dbPutTrackData(const TrackData& s) -> void; + auto dbGetTrackData(TrackId id) -> std::optional; + auto dbPutHash(const uint64_t& hash, TrackId i) -> void; + auto dbGetHash(const uint64_t& hash) -> std::optional; + auto dbPutTrack(TrackId id, const std::string& path, const uint64_t& hash) -> void; template @@ -128,9 +128,9 @@ class Database { }; template <> -auto Database::ParseRecord(const leveldb::Slice& key, - const leveldb::Slice& val) - -> std::optional; +auto Database::ParseRecord(const leveldb::Slice& key, + const leveldb::Slice& val) + -> std::optional; template <> auto Database::ParseRecord(const leveldb::Slice& key, const leveldb::Slice& val) 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; +auto ParseDataValue(const leveldb::Slice& slice) -> std::optional; /* 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; +auto ParseHashValue(const leveldb::Slice&) -> std::optional; -/* 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; +auto BytesToTrackId(const std::string& bytes) -> std::optional; } // 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 - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include - -#include -#include -#include - -#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 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 artist; - std::optional album; - - std::optional channels; - std::optional sample_rate; - std::optional 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 -#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 + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include + +#include +#include +#include + +#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 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 artist; + std::optional album; + + std::optional channels; + std::optional sample_rate; + std::optional 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 { +auto ParseDataValue(const leveldb::Slice& slice) -> std::optional { CborParser parser; CborValue container; CborError err; @@ -135,7 +135,7 @@ auto ParseDataValue(const leveldb::Slice& slice) -> std::optional { 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 { 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 { - return BytesToSongId(slice.ToString()); +auto ParseHashValue(const leveldb::Slice& slice) -> std::optional { + 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 { +auto BytesToTrackId(const std::string& bytes) -> std::optional { CborParser parser; CborValue val; cbor_parser_init(reinterpret_cast(bytes.data()), bytes.size(), diff --git a/src/database/tag_parser.cpp b/src/database/tag_parser.cpp index 589c988f..5bca0b58 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,7 +71,7 @@ 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 { libtags::Aux aux; aux.tags = out; 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 songs; + std::map 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 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 db(open_res.value()); SECTION("empty database") { - std::unique_ptr> res(db->GetSongs(10).get()); + std::unique_ptr> 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> res(db->GetSongs(10).get()); + std::unique_ptr> 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> new_res(db->GetSongs(10).get()); + std::unique_ptr> 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> new_res(db->GetSongs(10).get()); + std::unique_ptr> 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> new_res(db->GetSongs(10).get()); + std::unique_ptr> 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> new_res(db->GetSongs(10).get()); + std::unique_ptr> 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> new_res(db->GetSongs(10).get()); + std::unique_ptr> 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> new_res(db->GetSongs(10).get()); + std::unique_ptr> 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> res(db->GetSongs(1).get()); + SECTION("get tracks with pagination") { + std::unique_ptr> 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 res = BytesToSongId("i'm gay"); + std::optional 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 res = ParseDataValue("hi!"); + std::optional 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 res = ParseHashValue("the first song :)"); + std::optional res = ParseHashValue("the first track :)"); CHECK(res.has_value() == false); } diff --git a/src/database/song.cpp b/src/database/track.cpp similarity index 62% rename from src/database/song.cpp rename to 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 @@ -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; }