summaryrefslogtreecommitdiff
path: root/src/database/include/track.hpp
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2023-06-15 10:42:28 +1000
committerjacqueline <me@jacqueline.id.au>2023-06-15 10:42:28 +1000
commitc6bb42cdd21b63accd20012373a8a0e41d8566f5 (patch)
tree7fdbab3c5f1e285b54ea4949a31db41602b93b83 /src/database/include/track.hpp
parent0024bb1dbe0df319bc7bf022f0c4614cc9c8e0ed (diff)
downloadtangara-fw-c6bb42cdd21b63accd20012373a8a0e41d8566f5.tar.gz
song -> track
Diffstat (limited to 'src/database/include/track.hpp')
-rw-r--r--src/database/include/track.hpp169
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