diff options
| author | jacqueline <me@jacqueline.id.au> | 2023-06-15 10:42:28 +1000 |
|---|---|---|
| committer | jacqueline <me@jacqueline.id.au> | 2023-06-15 10:42:28 +1000 |
| commit | c6bb42cdd21b63accd20012373a8a0e41d8566f5 (patch) | |
| tree | 7fdbab3c5f1e285b54ea4949a31db41602b93b83 /src/database/include/track.hpp | |
| parent | 0024bb1dbe0df319bc7bf022f0c4614cc9c8e0ed (diff) | |
| download | tangara-fw-c6bb42cdd21b63accd20012373a8a0e41d8566f5.tar.gz | |
song -> track
Diffstat (limited to 'src/database/include/track.hpp')
| -rw-r--r-- | src/database/include/track.hpp | 169 |
1 files changed, 169 insertions, 0 deletions
diff --git a/src/database/include/track.hpp b/src/database/include/track.hpp new file mode 100644 index 00000000..5a0c0ca8 --- /dev/null +++ b/src/database/include/track.hpp @@ -0,0 +1,169 @@ +/* + * Copyright 2023 jacqueline <me@jacqueline.id.au> + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include <stdint.h> + +#include <optional> +#include <string> +#include <utility> + +#include "leveldb/db.h" +#include "span.hpp" + +namespace database { + +/* + * Uniquely describes a single track within the database. This value will be + * consistent across database updates, and should ideally (but is not guaranteed + * to) endure even across a track being removed and re-added. + * + * Four billion tracks should be enough for anybody. + */ +typedef uint32_t TrackId; + +/* + * Audio file encodings that we are aware of. Used to select an appropriate + * decoder at play time. + * + * Values of this enum are persisted in this database, so it is probably never a + * good idea to change the int representation of an existing value. + */ +enum class Encoding { + kUnsupported = 0, + kMp3 = 1, + kWav = 2, + kOgg = 3, + kFlac = 4, +}; + +/* + * Owning container for tag-related track metadata that was extracted from a + * file. + */ +struct TrackTags { + Encoding encoding; + std::optional<std::string> title; + + // TODO(jacqueline): It would be nice to use shared_ptr's for the artist and + // album, since there's likely a fair number of duplicates for each + // (especially the former). + + std::optional<std::string> artist; + std::optional<std::string> album; + + std::optional<int> channels; + std::optional<int> sample_rate; + std::optional<int> bits_per_sample; + + /* + * Returns a hash of the 'identifying' tags of this track. That is, a hash + * that can be used to determine if one track is likely the same as another, + * across things like re-encoding, re-mastering, or moving the underlying + * file. + */ + auto Hash() const -> uint64_t; + + bool operator==(const TrackTags&) const = default; +}; + +/* + * Immutable owning container for all of the metadata we store for a particular + * track. This includes two main kinds of metadata: + * 1. static(ish) attributes, such as the id, path on disk, hash of the tags + * 2. dynamic attributes, such as the number of times this track has been + * played. + * + * Because a TrackData is immutable, it is thread safe but will not reflect any + * changes to the dynamic attributes that may happen after it was obtained. + * + * Tracks may be 'tombstoned'; this indicates that the track is no longer + * present at its previous location on disk, and we do not have any existing + * files with a matching tags_hash. When this is the case, we ignore this + * TrackData for most purposes. We keep the entry in our database so that we can + * properly restore dynamic attributes (such as play count) if the track later + * re-appears on disk. + */ +class TrackData { + private: + const TrackId id_; + const std::string filepath_; + const uint64_t tags_hash_; + const uint32_t play_count_; + const bool is_tombstoned_; + + public: + /* Constructor used when adding new tracks to the database. */ + TrackData(TrackId id, const std::string& path, uint64_t hash) + : id_(id), + filepath_(path), + tags_hash_(hash), + play_count_(0), + is_tombstoned_(false) {} + + TrackData(TrackId id, + const std::string& path, + uint64_t hash, + uint32_t play_count, + bool is_tombstoned) + : id_(id), + filepath_(path), + tags_hash_(hash), + play_count_(play_count), + is_tombstoned_(is_tombstoned) {} + + auto id() const -> TrackId { return id_; } + auto filepath() const -> std::string { return filepath_; } + auto play_count() const -> uint32_t { return play_count_; } + auto tags_hash() const -> uint64_t { return tags_hash_; } + auto is_tombstoned() const -> bool { return is_tombstoned_; } + + auto UpdateHash(uint64_t new_hash) const -> TrackData; + + /* + * Marks this track data as a 'tombstone'. Tombstoned tracks are not playable, + * and should not generally be shown to users. + */ + auto Entomb() const -> TrackData; + + /* + * Clears the tombstone bit of this track, and updates the path to reflect its + * new location. + */ + auto Exhume(const std::string& new_path) const -> TrackData; + + bool operator==(const TrackData&) const = default; +}; + +/* + * Immutable and owning combination of a track's tags and metadata. + * + * Note that instances of this class may have a fairly large memory impact, due + * to the large number of strings they own. Prefer to query the database again + * (which has its own caching layer), rather than retaining Track instances for + * a long time. + */ +class Track { + public: + Track(const TrackData& data, const TrackTags& tags) + : data_(data), tags_(tags) {} + Track(const Track& other) = default; + + auto data() const -> const TrackData& { return data_; } + auto tags() const -> const TrackTags& { return tags_; } + + bool operator==(const Track&) const = default; + Track operator=(const Track& other) const { return Track(other); } + + private: + const TrackData data_; + const TrackTags tags_; +}; + +void swap(Track& first, Track& second); + +} // namespace database |
