diff options
| author | jacqueline <me@jacqueline.id.au> | 2024-05-02 19:12:26 +1000 |
|---|---|---|
| committer | jacqueline <me@jacqueline.id.au> | 2024-05-02 19:12:26 +1000 |
| commit | 1573a8c4cde1cd9528b422b2dcc598e37ffe94a7 (patch) | |
| tree | d162822b8fd7054f81bace0c7a65ab4d5e6f93ef /src/database/include | |
| parent | a231fd1c8afedbeb14b0bc77d76bad61db986059 (diff) | |
| download | tangara-fw-1573a8c4cde1cd9528b422b2dcc598e37ffe94a7.tar.gz | |
WIP merge cyclically dependent components into one big component
Diffstat (limited to 'src/database/include')
| -rw-r--r-- | src/database/include/database.hpp | 244 | ||||
| -rw-r--r-- | src/database/include/db_events.hpp | 29 | ||||
| -rw-r--r-- | src/database/include/env_esp.hpp | 143 | ||||
| -rw-r--r-- | src/database/include/file_gatherer.hpp | 36 | ||||
| -rw-r--r-- | src/database/include/future_fetcher.hpp | 62 | ||||
| -rw-r--r-- | src/database/include/index.hpp | 78 | ||||
| -rw-r--r-- | src/database/include/records.hpp | 85 | ||||
| -rw-r--r-- | src/database/include/tag_parser.hpp | 44 | ||||
| -rw-r--r-- | src/database/include/track.hpp | 205 |
9 files changed, 0 insertions, 926 deletions
diff --git a/src/database/include/database.hpp b/src/database/include/database.hpp deleted file mode 100644 index 35b76a13..00000000 --- a/src/database/include/database.hpp +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include <stdint.h> -#include <sys/_stdint.h> -#include <cstdint> -#include <future> -#include <memory> -#include <optional> -#include <stack> -#include <string> -#include <utility> -#include <vector> - -#include "collation.hpp" -#include "cppbor.h" -#include "file_gatherer.hpp" -#include "index.hpp" -#include "leveldb/cache.h" -#include "leveldb/db.h" -#include "leveldb/iterator.h" -#include "leveldb/options.h" -#include "leveldb/slice.h" -#include "memory_resource.hpp" -#include "records.hpp" -#include "result.hpp" -#include "tag_parser.hpp" -#include "tasks.hpp" -#include "track.hpp" - -namespace database { - -const uint8_t kCurrentDbVersion = 6; - -struct SearchKey; -class Record; -class Iterator; - -/* - * Handle to an open database. This can be used to store large amounts of - * persistent data on the SD card, in a manner that can be retrieved later very - * quickly. - * - * A database includes a number of 'indexes'. Each index is a sorted, - * hierarchical view of all the playable tracks on the device. - */ -class Database { - public: - enum DatabaseError { - ALREADY_OPEN, - FAILED_TO_OPEN, - }; - static auto Open(IFileGatherer& file_gatherer, - ITagParser& tag_parser, - locale::ICollator& collator, - tasks::WorkerPool& bg_worker) - -> cpp::result<Database*, DatabaseError>; - - static auto Destroy() -> void; - - ~Database(); - - auto schemaVersion() -> std::string; - - auto sizeOnDiskBytes() -> size_t; - - /* Adds an arbitrary record to the database. */ - auto put(const std::string& key, const std::string& val) -> void; - - /* Retrives a value previously stored with `put`. */ - auto get(const std::string& key) -> std::optional<std::string>; - - auto getTrackPath(TrackId id) -> std::optional<std::string>; - auto getTrack(TrackId id) -> std::shared_ptr<Track>; - - auto getIndexes() -> std::vector<IndexInfo>; - auto updateIndexes() -> void; - auto isUpdating() -> bool; - - // Cannot be copied or moved. - Database(const Database&) = delete; - Database& operator=(const Database&) = delete; - - private: - friend class Iterator; - - // Owned. Dumb pointers because destruction needs to be done in an explicit - // order. - leveldb::DB* db_; - leveldb::Cache* cache_; - - // Not owned. - IFileGatherer& file_gatherer_; - ITagParser& tag_parser_; - locale::ICollator& collator_; - - std::atomic<bool> is_updating_; - - Database(leveldb::DB* db, - leveldb::Cache* cache, - IFileGatherer& file_gatherer, - ITagParser& tag_parser, - locale::ICollator& collator); - - auto dbMintNewTrackId() -> TrackId; - - auto dbEntomb(TrackId track, uint64_t hash) -> void; - auto dbPutTrackData(const TrackData& s) -> void; - auto dbGetTrackData(TrackId id) -> std::shared_ptr<TrackData>; - auto dbPutHash(const uint64_t& hash, TrackId i) -> void; - auto dbGetHash(const uint64_t& hash) -> std::optional<TrackId>; - - auto dbCreateIndexesForTrack(const Track& track) -> void; - auto dbRemoveIndexes(std::shared_ptr<TrackData>) -> void; - - auto dbIngestTagHashes(const TrackTags&, - std::pmr::unordered_map<Tag, uint64_t>&) -> void; - auto dbRecoverTagsFromHashes(const std::pmr::unordered_map<Tag, uint64_t>&) - -> std::shared_ptr<TrackTags>; - - auto getRecord(const SearchKey& c) - -> std::optional<std::pair<std::pmr::string, Record>>; - auto countRecords(const SearchKey& c) -> size_t; -}; - -/* - * Container for the data needed to iterate through database records. This is a - * lower-level type that the higher-level iterators are built from; most users - * outside this namespace shouldn't need to work with continuations. - */ -struct SearchKey { - std::pmr::string prefix; - /* If not given, then iteration starts from `prefix`. */ - std::optional<std::pmr::string> key; - int offset; - - auto startKey() const -> std::string_view; -}; - -/* - * A record belonging to one of the database's indexes. This may either be a - * leaf record, containing a track id, or a branch record, containing a new - * Header to retrieve results at the next level of the index. - */ -class Record { - public: - Record(const IndexKey&, const leveldb::Slice&); - - Record(const Record&) = default; - Record& operator=(const Record& other) = default; - - auto text() const -> std::string_view; - auto contents() const -> const std::variant<TrackId, IndexKey::Header>&; - - private: - std::pmr::string text_; - std::variant<TrackId, IndexKey::Header> contents_; -}; - -/* - * Utility for accessing a large set of database records, one record at a time. - */ -class Iterator { - public: - Iterator(std::shared_ptr<Database>, IndexId); - Iterator(std::shared_ptr<Database>, const IndexKey::Header&); - - Iterator(const Iterator&) = default; - Iterator& operator=(const Iterator& other) = default; - - auto value() const -> const std::optional<Record>&; - std::optional<Record> operator*() const { return value(); } - - auto next() -> void; - std::optional<Record> operator++() { - next(); - return value(); - } - std::optional<Record> operator++(int) { - auto val = value(); - next(); - return val; - } - - auto prev() -> void; - std::optional<Record> operator--() { - prev(); - return value(); - } - std::optional<Record> operator--(int) { - auto val = value(); - prev(); - return val; - } - - auto count() const -> size_t; - - private: - auto iterate(const SearchKey& key) -> void; - - friend class TrackIterator; - - std::weak_ptr<Database> db_; - SearchKey key_; - std::optional<Record> current_; -}; - -class TrackIterator { - public: - TrackIterator(const Iterator&); - - TrackIterator(const TrackIterator&) = default; - TrackIterator& operator=(TrackIterator&& other) = default; - - auto value() const -> std::optional<TrackId>; - std::optional<TrackId> operator*() const { return value(); } - - auto next() -> void; - std::optional<TrackId> operator++() { - next(); - return value(); - } - std::optional<TrackId> operator++(int) { - auto val = value(); - next(); - return val; - } - - auto count() const -> size_t; - - private: - TrackIterator(std::weak_ptr<Database>); - auto next(bool advance) -> void; - - std::weak_ptr<Database> db_; - std::vector<Iterator> levels_; -}; - -} // namespace database diff --git a/src/database/include/db_events.hpp b/src/database/include/db_events.hpp deleted file mode 100644 index a1aefc27..00000000 --- a/src/database/include/db_events.hpp +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include <stdint.h> -#include "tinyfsm.hpp" - -namespace database { -namespace event { - -struct UpdateStarted : tinyfsm::Event {}; - -struct UpdateFinished : tinyfsm::Event {}; - -struct UpdateProgress : tinyfsm::Event { - enum class Stage { - kVerifyingExistingTracks, - kScanningForNewTracks, - }; - Stage stage; - uint64_t val; -}; - -} // namespace event -} // namespace database diff --git a/src/database/include/env_esp.hpp b/src/database/include/env_esp.hpp deleted file mode 100644 index 472a72a6..00000000 --- a/src/database/include/env_esp.hpp +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include <memory> -#include <mutex> -#include <set> -#include <string> - -#include "leveldb/env.h" -#include "leveldb/status.h" - -#include "tasks.hpp" - -namespace leveldb { - -extern tasks::WorkerPool* sBackgroundThread; - -// Tracks the files locked by EspEnv::LockFile(). -// -// We maintain a separate set instead of relying on fcntl(F_SETLK) because -// fcntl(F_SETLK) does not provide any protection against multiple uses from the -// same process. -// -// Instances are thread-safe because all member data is guarded by a mutex. -class InMemoryLockTable { - public: - bool Insert(const std::string& fname) { - mu_.lock(); - bool succeeded = locked_files_.insert(fname).second; - mu_.unlock(); - return succeeded; - } - void Remove(const std::string& fname) { - mu_.lock(); - locked_files_.erase(fname); - mu_.unlock(); - } - - private: - std::mutex mu_; - std::set<std::string> locked_files_; -}; - -class EspEnv : public leveldb::Env { - public: - EspEnv(); - ~EspEnv() override; - - Status NewSequentialFile(const std::string& filename, - SequentialFile** result) override; - - Status NewRandomAccessFile(const std::string& filename, - RandomAccessFile** result) override; - - Status NewWritableFile(const std::string& filename, - WritableFile** result) override; - - Status NewAppendableFile(const std::string& filename, - WritableFile** result) override; - - bool FileExists(const std::string& filename) override; - - Status GetChildren(const std::string& directory_path, - std::vector<std::string>* result) override; - - Status RemoveFile(const std::string& filename) override; - - Status CreateDir(const std::string& dirname) override; - - Status RemoveDir(const std::string& dirname) override; - - Status GetFileSize(const std::string& filename, uint64_t* size) override; - - Status RenameFile(const std::string& from, const std::string& to) override; - - Status LockFile(const std::string& filename, FileLock** lock) override; - - Status UnlockFile(FileLock* lock) override; - - void Schedule(void (*background_work_function)(void* background_work_arg), - void* background_work_arg) override; - - void StartThread(void (*thread_main)(void* thread_main_arg), - void* thread_main_arg) override; - - Status GetTestDirectory(std::string* result) override; - - Status NewLogger(const std::string& filename, Logger** result) override; - - uint64_t NowMicros() override; - - void SleepForMicroseconds(int micros) override; - - void BackgroundThreadMain(); - - private: - InMemoryLockTable locks_; // Thread-safe. -}; - -} // namespace leveldb - -namespace database { - -// Wraps an Env instance whose destructor is never created. -// -// Intended usage: -// using PlatformSingletonEnv = SingletonEnv<PlatformEnv>; -// void ConfigurePosixEnv(int param) { -// PlatformSingletonEnv::AssertEnvNotInitialized(); -// // set global configuration flags. -// } -// Env* Env::Default() { -// static PlatformSingletonEnv default_env; -// return default_env.env(); -// } -template <typename EnvType> -class SingletonEnv { - public: - SingletonEnv() { - static_assert(sizeof(env_storage_) >= sizeof(EnvType), - "env_storage_ will not fit the Env"); - static_assert(alignof(decltype(env_storage_)) >= alignof(EnvType), - "env_storage_ does not meet the Env's alignment needs"); - new (&env_storage_) EnvType(); - } - ~SingletonEnv() = default; - - SingletonEnv(const SingletonEnv&) = delete; - SingletonEnv& operator=(const SingletonEnv&) = delete; - - leveldb::Env* env() { return reinterpret_cast<leveldb::Env*>(&env_storage_); } - - private: - typename std::aligned_storage<sizeof(EnvType), alignof(EnvType)>::type - env_storage_; -}; - -} // namespace database diff --git a/src/database/include/file_gatherer.hpp b/src/database/include/file_gatherer.hpp deleted file mode 100644 index 685bdb2c..00000000 --- a/src/database/include/file_gatherer.hpp +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include <deque> -#include <functional> -#include <sstream> -#include <string> - -#include "ff.h" - -namespace database { - -class IFileGatherer { - public: - virtual ~IFileGatherer(){}; - - virtual auto FindFiles( - const std::string& root, - std::function<void(std::string_view, const FILINFO&)> cb) - -> void = 0; -}; - -class FileGathererImpl : public IFileGatherer { - public: - virtual auto FindFiles( - const std::string& root, - std::function<void(std::string_view, const FILINFO&)> cb) - -> void override; -}; - -} // namespace database diff --git a/src/database/include/future_fetcher.hpp b/src/database/include/future_fetcher.hpp deleted file mode 100644 index e8ce9729..00000000 --- a/src/database/include/future_fetcher.hpp +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include <memory> -#include <utility> - -#include "database.hpp" - -namespace database { - -/* - * Utility to simplify waiting for a std::future to complete without blocking. - * Each instance is good for a single future, and does not directly own anything - * other than the future itself. - */ -template <typename T> -class FutureFetcher { - public: - explicit FutureFetcher(std::future<T>&& fut) - : is_consumed_(false), fut_(std::move(fut)) {} - - /* - * Returns whether or not the underlying future is still awaiting async work. - */ - auto Finished() -> bool { - if (!fut_.valid()) { - return true; - } - if (fut_.wait_for(std::chrono::seconds(0)) != std::future_status::ready) { - return false; - } - return true; - } - - /* - * Returns the result of the future, and releases ownership of the underling - * resource. Will return an absent value if the future became invalid (e.g. - * the promise associated with it was destroyed.) - */ - auto Result() -> std::optional<T> { - assert(!is_consumed_); - if (is_consumed_) { - return {}; - } - is_consumed_ = true; - if (!fut_.valid()) { - return {}; - } - return fut_.get(); - } - - private: - bool is_consumed_; - std::future<T> fut_; -}; - -} // namespace database diff --git a/src/database/include/index.hpp b/src/database/include/index.hpp deleted file mode 100644 index 45dae464..00000000 --- a/src/database/include/index.hpp +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include <stdint.h> - -#include <cstdint> -#include <string> -#include <variant> -#include <vector> - -#include "collation.hpp" -#include "leveldb/db.h" -#include "leveldb/slice.h" - -#include "leveldb/write_batch.h" -#include "memory_resource.hpp" -#include "track.hpp" - -namespace database { - -typedef uint8_t IndexId; - -struct IndexInfo { - // Unique id for this index - IndexId id; - // Localised, user-friendly description of this index. e.g. "Albums by Artist" - // or "All Tracks". - std::pmr::string name; - // Specifier for how this index breaks down the database. - std::vector<Tag> components; -}; - -struct IndexKey { - struct Header { - // The index that this key was created for. - IndexId id; - // The number of components of IndexInfo that have already been filtered. - // For example, if an index consists of { kGenre, kArtist }, and this key - // represents an artist, then depth = 1. - std::uint8_t depth; - // The cumulative hash of all filtered components, in order. For example, if - // an index consists of { kArtist, kAlbum, kTitle }, and we are at depth = 2 - // then this may contain hash(hash("Jacqueline"), "My Cool Album"). - std::uint64_t components_hash; - - bool operator==(const Header&) const = default; - }; - Header header; - - // The filterable / selectable item that this key represents. "Jacqueline" for - // kArtist, "My Cool Album" for kAlbum, etc. - std::optional<std::pmr::string> item; - // If this is a leaf component, the track id for this record. - // This could reasonably be the value for a record, but we keep it as a part - // of the key to help with disambiguation. - std::optional<TrackId> track; -}; - -auto Index(locale::ICollator&, const IndexInfo&, const Track&) - -> std::vector<std::pair<IndexKey, std::string>>; - -auto ExpandHeader(const IndexKey::Header&, - const std::optional<std::pmr::string>&) -> IndexKey::Header; - -// Predefined indexes -// TODO(jacqueline): Make these defined at runtime! :) - -extern const IndexInfo kAlbumsByArtist; -extern const IndexInfo kTracksByGenre; -extern const IndexInfo kAllTracks; -extern const IndexInfo kAllAlbums; - -} // namespace database diff --git a/src/database/include/records.hpp b/src/database/include/records.hpp deleted file mode 100644 index 3ca68fea..00000000 --- a/src/database/include/records.hpp +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include <stdint.h> - -#include <string> -#include <variant> -#include <vector> - -#include "leveldb/db.h" -#include "leveldb/slice.h" - -#include "index.hpp" -#include "memory_resource.hpp" -#include "track.hpp" - -namespace database { - -auto EncodePathKey(std::string_view path) -> std::string; - -/* - * Returns the prefix added to every TrackData key. This can be used to iterate - * over every data record in the database. - */ -auto EncodeDataPrefix() -> std::string; - -/* Encodes a data key for a track with the specified id. */ -auto EncodeDataKey(const TrackId& id) -> std::string; - -/* - * 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 EncodeDataValue(const TrackData& track) -> std::string; - -/* - * Parses bytes previously encoded via EncodeDataValue back into a TrackData. - * May return nullopt if parsing fails. - */ -auto ParseDataValue(const leveldb::Slice& slice) -> std::shared_ptr<TrackData>; - -/* Encodes a hash key for the specified hash. */ -auto EncodeHashKey(const uint64_t& hash) -> std::string; - -/* - * 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 EncodeHashValue(TrackId id) -> std::string; - -/* Encodes a hash key for the specified hash. */ -auto EncodeTagHashKey(const uint64_t& hash) -> std::string; - -/* - * Parses bytes previously encoded via EncodeHashValue back into a track id. May - * return nullopt if parsing fails. - */ -auto ParseHashValue(const leveldb::Slice&) -> std::optional<TrackId>; - -/* Encodes a prefix that matches all index keys, of all ids and depths. */ -auto EncodeAllIndexesPrefix() -> std::string; - -/* - */ -auto EncodeIndexPrefix(const IndexKey::Header&) -> std::string; - -auto EncodeIndexKey(const IndexKey&) -> std::string; -auto ParseIndexKey(const leveldb::Slice&) -> std::optional<IndexKey>; - -/* Encodes a TrackId as bytes. */ -auto TrackIdToBytes(TrackId id) -> std::string; - -/* - * Converts a track id encoded via TrackIdToBytes back into a TrackId. May - * return nullopt if parsing fails. - */ -auto BytesToTrackId(std::span<const char> bytes) -> std::optional<TrackId>; - -} // namespace database diff --git a/src/database/include/tag_parser.hpp b/src/database/include/tag_parser.hpp deleted file mode 100644 index 966258b5..00000000 --- a/src/database/include/tag_parser.hpp +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include <string> - -#include "lru_cache.hpp" -#include "track.hpp" - -namespace database { - -class ITagParser { - public: - virtual ~ITagParser() {} - virtual auto ReadAndParseTags(std::string_view path) - -> std::shared_ptr<TrackTags> = 0; -}; - -class TagParserImpl : public ITagParser { - public: - TagParserImpl(); - auto ReadAndParseTags(std::string_view path) - -> std::shared_ptr<TrackTags> override; - - private: - auto parseNew(std::string_view path) -> std::shared_ptr<TrackTags>; - - /* - * Cache of tags that have already been extracted from files. Ideally this - * cache should be slightly larger than any page sizes in the UI. - */ - std::mutex cache_mutex_; - util::LruCache<8, std::pmr::string, std::shared_ptr<TrackTags>> cache_; - - // We could also consider keeping caches of artist name -> std::string and - // similar. This hasn't been done yet, as this isn't a common workload in - // any of our UI. -}; - -} // namespace database diff --git a/src/database/include/track.hpp b/src/database/include/track.hpp deleted file mode 100644 index b097ab52..00000000 --- a/src/database/include/track.hpp +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include <cstdint> - -#include <map> -#include <memory> -#include <optional> -#include <span> -#include <string> -#include <unordered_map> -#include <utility> -#include <variant> - -#include "leveldb/db.h" -#include "memory_resource.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 Container { - kUnsupported = 0, - kMp3 = 1, - kWav = 2, - kOgg = 3, - kFlac = 4, - kOpus = 5, -}; - -enum class Tag { - kTitle = 0, - kArtist = 1, - kAlbum = 2, - kAlbumArtist = 3, - kDisc = 4, - kTrack = 5, - kAlbumOrder = 6, - kGenres = 7, -}; - -using TagValue = std::variant<std::monostate, - std::pmr::string, - uint32_t, - std::span<const std::pmr::string>>; - -auto tagName(Tag) -> std::string; -auto tagHash(const TagValue&) -> uint64_t; -auto tagToString(const TagValue&) -> std::string; - -/* - * Owning container for tag-related track metadata that was extracted from a - * file. - */ -class TrackTags { - public: - static auto create() -> std::shared_ptr<TrackTags>; - - TrackTags() - : encoding_(Container::kUnsupported), genres_(&memory::kSpiRamResource) {} - - TrackTags(const TrackTags& other) = delete; - TrackTags& operator=(TrackTags& other) = delete; - - bool operator==(const TrackTags&) const = default; - - auto get(Tag) const -> TagValue; - auto set(Tag, std::string_view) -> void; - - auto allPresent() const -> std::vector<Tag>; - - auto encoding() const -> Container { return encoding_; }; - auto encoding(Container e) -> void { encoding_ = e; }; - - auto title() const -> const std::optional<std::pmr::string>&; - auto title(std::string_view) -> void; - - auto artist() const -> const std::optional<std::pmr::string>&; - auto artist(std::string_view) -> void; - - auto album() const -> const std::optional<std::pmr::string>&; - auto album(std::string_view) -> void; - - auto albumArtist() const -> const std::optional<std::pmr::string>&; - auto albumArtist(std::string_view) -> void; - - auto disc() const -> const std::optional<uint8_t>&; - auto disc(const std::string_view) -> void; - - auto track() const -> const std::optional<uint16_t>&; - auto track(const std::string_view) -> void; - - auto albumOrder() const -> uint32_t; - - auto genres() const -> std::span<const std::pmr::string>; - auto genres(const std::string_view) -> void; - - /* - * 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; - - private: - Container encoding_; - - std::optional<std::pmr::string> title_; - std::optional<std::pmr::string> artist_; - std::optional<std::pmr::string> album_; - std::optional<std::pmr::string> album_artist_; - std::optional<uint8_t> disc_; - std::optional<uint16_t> track_; - std::pmr::vector<std::pmr::string> genres_; -}; - -/* - * 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. - */ -struct TrackData { - public: - TrackData() - : id(0), - filepath(), - tags_hash(0), - individual_tag_hashes(&memory::kSpiRamResource), - is_tombstoned(false), - modified_at() {} - - TrackId id; - std::pmr::string filepath; - uint64_t tags_hash; - std::pmr::unordered_map<Tag, uint64_t> individual_tag_hashes; - bool is_tombstoned; - std::pair<uint16_t, uint16_t> modified_at; - - TrackData(TrackData&& other) = delete; - TrackData& operator=(TrackData& other) = delete; - - 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(std::shared_ptr<TrackData>& data, std::shared_ptr<TrackTags> tags) - : data_(data), tags_(tags) {} - - Track(Track& other) = delete; - Track& operator=(Track& other) = delete; - - bool operator==(const Track&) const = default; - - auto data() const -> const TrackData& { return *data_; } - auto tags() const -> const TrackTags& { return *tags_; } - - auto TitleOrFilename() const -> std::pmr::string; - - private: - std::shared_ptr<const TrackData> data_; - std::shared_ptr<TrackTags> tags_; -}; - -} // namespace database |
