Fork of Tangara with customizations
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
tangara-fw/src/database/include/track.hpp

169 lines
4.9 KiB

/*
* 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