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.
169 lines
4.9 KiB
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
|
|
|