parent
0024bb1dbe
commit
c6bb42cdd2
@ -1,166 +0,0 @@ |
|||||||
/*
|
|
||||||
* 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 song within the database. This value will be |
|
||||||
* consistent across database updates, and should ideally (but is not guaranteed |
|
||||||
* to) endure even across a song being removed and re-added. |
|
||||||
* |
|
||||||
* Four billion songs should be enough for anybody. |
|
||||||
*/ |
|
||||||
typedef uint32_t SongId; |
|
||||||
|
|
||||||
/*
|
|
||||||
* 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 song metadata that was extracted from a |
|
||||||
* file. |
|
||||||
*/ |
|
||||||
struct SongTags { |
|
||||||
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 song. That is, a hash that |
|
||||||
* can be used to determine if one song 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 SongTags&) const = default; |
|
||||||
}; |
|
||||||
|
|
||||||
/*
|
|
||||||
* Immutable owning container for all of the metadata we store for a particular |
|
||||||
* song. 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 song has been |
|
||||||
* played. |
|
||||||
* |
|
||||||
* Because a SongData is immutable, it is thread safe but will not reflect any |
|
||||||
* changes to the dynamic attributes that may happen after it was obtained. |
|
||||||
* |
|
||||||
* Songs may be 'tombstoned'; this indicates that the song 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 SongData for most |
|
||||||
* purposes. We keep the entry in our database so that we can properly restore |
|
||||||
* dynamic attributes (such as play count) if the song later re-appears on disk. |
|
||||||
*/ |
|
||||||
class SongData { |
|
||||||
private: |
|
||||||
const SongId 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 songs to the database. */ |
|
||||||
SongData(SongId id, const std::string& path, uint64_t hash) |
|
||||||
: id_(id), |
|
||||||
filepath_(path), |
|
||||||
tags_hash_(hash), |
|
||||||
play_count_(0), |
|
||||||
is_tombstoned_(false) {} |
|
||||||
|
|
||||||
SongData(SongId 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 -> SongId { 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 -> SongData; |
|
||||||
|
|
||||||
/*
|
|
||||||
* Marks this song data as a 'tombstone'. Tombstoned songs are not playable, |
|
||||||
* and should not generally be shown to users. |
|
||||||
*/ |
|
||||||
auto Entomb() const -> SongData; |
|
||||||
|
|
||||||
/*
|
|
||||||
* Clears the tombstone bit of this song, and updates the path to reflect its |
|
||||||
* new location. |
|
||||||
*/ |
|
||||||
auto Exhume(const std::string& new_path) const -> SongData; |
|
||||||
|
|
||||||
bool operator==(const SongData&) const = default; |
|
||||||
}; |
|
||||||
|
|
||||||
/*
|
|
||||||
* Immutable and owning combination of a song'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 Song instances for a |
|
||||||
* long time. |
|
||||||
*/ |
|
||||||
class Song { |
|
||||||
public: |
|
||||||
Song(const SongData& data, const SongTags& tags) : data_(data), tags_(tags) {} |
|
||||||
Song(const Song& other) = default; |
|
||||||
|
|
||||||
auto data() const -> const SongData& { return data_; } |
|
||||||
auto tags() const -> const SongTags& { return tags_; } |
|
||||||
|
|
||||||
bool operator==(const Song&) const = default; |
|
||||||
Song operator=(const Song& other) const { return Song(other); } |
|
||||||
|
|
||||||
private: |
|
||||||
const SongData data_; |
|
||||||
const SongTags tags_; |
|
||||||
}; |
|
||||||
|
|
||||||
void swap(Song& first, Song& second); |
|
||||||
|
|
||||||
} // namespace database
|
|
@ -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
|
Loading…
Reference in new issue