summaryrefslogtreecommitdiff
path: root/src/database/include
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2024-05-02 19:12:26 +1000
committerjacqueline <me@jacqueline.id.au>2024-05-02 19:12:26 +1000
commit1573a8c4cde1cd9528b422b2dcc598e37ffe94a7 (patch)
treed162822b8fd7054f81bace0c7a65ab4d5e6f93ef /src/database/include
parenta231fd1c8afedbeb14b0bc77d76bad61db986059 (diff)
downloadtangara-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.hpp244
-rw-r--r--src/database/include/db_events.hpp29
-rw-r--r--src/database/include/env_esp.hpp143
-rw-r--r--src/database/include/file_gatherer.hpp36
-rw-r--r--src/database/include/future_fetcher.hpp62
-rw-r--r--src/database/include/index.hpp78
-rw-r--r--src/database/include/records.hpp85
-rw-r--r--src/database/include/tag_parser.hpp44
-rw-r--r--src/database/include/track.hpp205
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