Use an mutable struct + const instead of an immutable class

custom
jacqueline 2 years ago
parent 5b5b792467
commit 4f8c127da9
  1. 67
      src/database/database.cpp
  2. 73
      src/database/include/track.hpp
  3. 2
      src/database/index.cpp
  4. 27
      src/database/records.cpp
  5. 23
      src/database/track.cpp
  6. 2
      src/ui/screen_playing.cpp

@ -202,8 +202,8 @@ auto Database::Update() -> std::future<void> {
continue; continue;
} }
if (track->is_tombstoned()) { if (track->is_tombstoned) {
ESP_LOGW(kTag, "skipping tombstoned %lx", track->id()); ESP_LOGW(kTag, "skipping tombstoned %lx", track->id);
it->Next(); it->Next();
continue; continue;
} }
@ -212,25 +212,28 @@ auto Database::Update() -> std::future<void> {
FILINFO info; FILINFO info;
{ {
auto lock = drivers::acquire_spi(); auto lock = drivers::acquire_spi();
res = f_stat(track->filepath().c_str(), &info); res = f_stat(track->filepath.c_str(), &info);
} }
std::pair<uint16_t, uint16_t> modified_at{0, 0}; std::pair<uint16_t, uint16_t> modified_at{0, 0};
if (res == FR_OK) { if (res == FR_OK) {
modified_at = {info.fdate, info.ftime}; modified_at = {info.fdate, info.ftime};
} }
if (modified_at == track->modified_at()) { if (modified_at == track->modified_at) {
newest_track = std::max(modified_at, newest_track); newest_track = std::max(modified_at, newest_track);
} else {
track->modified_at = modified_at;
} }
std::shared_ptr<TrackTags> tags = std::shared_ptr<TrackTags> tags =
tag_parser_.ReadAndParseTags(track->filepath()); tag_parser_.ReadAndParseTags(track->filepath);
if (!tags || tags->encoding() == Container::kUnsupported) { if (!tags || tags->encoding() == Container::kUnsupported) {
// We couldn't read the tags for this track. Either they were // We couldn't read the tags for this track. Either they were
// malformed, or perhaps the file is missing. Either way, tombstone // malformed, or perhaps the file is missing. Either way, tombstone
// this record. // this record.
ESP_LOGW(kTag, "entombing missing #%lx", track->id()); ESP_LOGW(kTag, "entombing missing #%lx", track->id);
dbPutTrackData(track->Entomb().UpdateModifiedAt(modified_at)); track->is_tombstoned = true;
dbPutTrackData(*track);
it->Next(); it->Next();
continue; continue;
} }
@ -239,15 +242,15 @@ auto Database::Update() -> std::future<void> {
// location. All that's left to do is update any metadata about it. // location. All that's left to do is update any metadata about it.
uint64_t new_hash = tags->Hash(); uint64_t new_hash = tags->Hash();
if (new_hash != track->tags_hash()) { if (new_hash != track->tags_hash) {
// This track's tags have changed. Since the filepath is exactly the // This track's tags have changed. Since the filepath is exactly the
// same, we assume this is a legitimate correction. Update the // same, we assume this is a legitimate correction. Update the
// database. // database.
ESP_LOGI(kTag, "updating hash (%llx -> %llx)", track->tags_hash(), ESP_LOGI(kTag, "updating hash (%llx -> %llx)", track->tags_hash,
new_hash); new_hash);
dbPutTrackData( track->tags_hash = new_hash;
track->UpdateHash(new_hash).UpdateModifiedAt(modified_at)); dbPutTrackData(*track);
dbPutHash(new_hash, track->id()); dbPutHash(new_hash, track->id);
} }
Track t{track, tags}; Track t{track, tags};
@ -274,7 +277,6 @@ auto Database::Update() -> std::future<void> {
std::pair<uint16_t, uint16_t> modified{info.fdate, info.ftime}; std::pair<uint16_t, uint16_t> modified{info.fdate, info.ftime};
if (modified < newest_track) { if (modified < newest_track) {
ESP_LOGI(kTag, "skipping old file");
return; return;
} }
@ -299,7 +301,11 @@ auto Database::Update() -> std::future<void> {
TrackId id = dbMintNewTrackId(); TrackId id = dbMintNewTrackId();
ESP_LOGI(kTag, "recording new 0x%lx", id); ESP_LOGI(kTag, "recording new 0x%lx", id);
auto data = std::make_shared<TrackData>(id, path, hash, modified); auto data = std::make_shared<TrackData>();
data->id = id;
data->filepath = path;
data->tags_hash = hash;
data->modified_at = modified;
dbPutTrackData(*data); dbPutTrackData(*data);
dbPutHash(hash, id); dbPutHash(hash, id);
@ -311,22 +317,27 @@ auto Database::Update() -> std::future<void> {
std::shared_ptr<TrackData> existing_data = dbGetTrackData(*existing_hash); std::shared_ptr<TrackData> existing_data = dbGetTrackData(*existing_hash);
if (!existing_data) { if (!existing_data) {
// We found a hash that matches, but there's no data record? Weird. // We found a hash that matches, but there's no data record? Weird.
auto new_data = auto new_data = std::make_shared<TrackData>();
std::make_shared<TrackData>(*existing_hash, path, hash, modified); new_data->id = dbMintNewTrackId();
new_data->filepath = path;
new_data->tags_hash = hash;
new_data->modified_at = modified;
dbPutTrackData(*new_data); dbPutTrackData(*new_data);
auto t = std::make_shared<Track>(new_data, tags); auto t = std::make_shared<Track>(new_data, tags);
dbCreateIndexesForTrack(*t); dbCreateIndexesForTrack(*t);
return; return;
} }
if (existing_data->is_tombstoned()) { if (existing_data->is_tombstoned) {
ESP_LOGI(kTag, "exhuming track %lu", existing_data->id()); ESP_LOGI(kTag, "exhuming track %lu", existing_data->id);
dbPutTrackData(existing_data->Exhume(path)); existing_data->is_tombstoned = false;
existing_data->modified_at = modified;
dbPutTrackData(*existing_data);
auto t = std::make_shared<Track>(existing_data, tags); auto t = std::make_shared<Track>(existing_data, tags);
dbCreateIndexesForTrack(*t); dbCreateIndexesForTrack(*t);
} else if (existing_data->filepath() != path) { } else if (existing_data->filepath != path) {
ESP_LOGW(kTag, "tag hash collision for %s and %s", ESP_LOGW(kTag, "tag hash collision for %s and %s",
existing_data->filepath().c_str(), path.c_str()); existing_data->filepath.c_str(), path.c_str());
ESP_LOGI(kTag, "hash components: %s, %s, %s", ESP_LOGI(kTag, "hash components: %s, %s, %s",
tags->at(Tag::kTitle).value_or("no title").c_str(), tags->at(Tag::kTitle).value_or("no title").c_str(),
tags->at(Tag::kArtist).value_or("no artist").c_str(), tags->at(Tag::kArtist).value_or("no artist").c_str(),
@ -343,7 +354,7 @@ auto Database::GetTrackPath(TrackId id)
[=, this]() -> std::optional<std::pmr::string> { [=, this]() -> std::optional<std::pmr::string> {
auto track_data = dbGetTrackData(id); auto track_data = dbGetTrackData(id);
if (track_data) { if (track_data) {
return track_data->filepath(); return track_data->filepath;
} }
return {}; return {};
}); });
@ -353,11 +364,11 @@ auto Database::GetTrack(TrackId id) -> std::future<std::shared_ptr<Track>> {
return worker_task_->Dispatch<std::shared_ptr<Track>>( return worker_task_->Dispatch<std::shared_ptr<Track>>(
[=, this]() -> std::shared_ptr<Track> { [=, this]() -> std::shared_ptr<Track> {
std::shared_ptr<TrackData> data = dbGetTrackData(id); std::shared_ptr<TrackData> data = dbGetTrackData(id);
if (!data || data->is_tombstoned()) { if (!data || data->is_tombstoned) {
return {}; return {};
} }
std::shared_ptr<TrackTags> tags = std::shared_ptr<TrackTags> tags =
tag_parser_.ReadAndParseTags(data->filepath()); tag_parser_.ReadAndParseTags(data->filepath);
if (!tags) { if (!tags) {
return {}; return {};
} }
@ -505,10 +516,10 @@ auto Database::dbEntomb(TrackId id, uint64_t hash) -> void {
} }
auto Database::dbPutTrackData(const TrackData& s) -> void { auto Database::dbPutTrackData(const TrackData& s) -> void {
std::string key = EncodeDataKey(s.id()); std::string key = EncodeDataKey(s.id);
std::string val = EncodeDataValue(s); std::string val = EncodeDataValue(s);
if (!db_->Put(leveldb::WriteOptions(), key, val).ok()) { if (!db_->Put(leveldb::WriteOptions(), key, val).ok()) {
ESP_LOGE(kTag, "failed to write data for #%lx", s.id()); ESP_LOGE(kTag, "failed to write data for #%lx", s.id);
} }
} }
@ -682,11 +693,11 @@ auto Database::ParseRecord<Track>(const leveldb::Slice& key,
const leveldb::Slice& val) const leveldb::Slice& val)
-> std::shared_ptr<Track> { -> std::shared_ptr<Track> {
std::shared_ptr<TrackData> data = ParseDataValue(val); std::shared_ptr<TrackData> data = ParseDataValue(val);
if (!data || data->is_tombstoned()) { if (!data || data->is_tombstoned) {
return {}; return {};
} }
std::shared_ptr<TrackTags> tags = std::shared_ptr<TrackTags> tags =
tag_parser_.ReadAndParseTags(data->filepath()); tag_parser_.ReadAndParseTags(data->filepath);
if (!tags) { if (!tags) {
return {}; return {};
} }

@ -97,8 +97,8 @@ class TrackTags {
}; };
/* /*
* Immutable owning container for all of the metadata we store for a particular * Owning container for all of the metadata we store for a particular track.
* track. This includes two main kinds of metadata: * This includes two main kinds of metadata:
* 1. static(ish) attributes, such as the id, path on disk, hash of the tags * 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 * 2. dynamic attributes, such as the number of times this track has been
* played. * played.
@ -113,66 +113,25 @@ class TrackTags {
* properly restore dynamic attributes (such as play count) if the track later * properly restore dynamic attributes (such as play count) if the track later
* re-appears on disk. * re-appears on disk.
*/ */
class TrackData { struct TrackData {
private:
const TrackId id_;
const std::pmr::string filepath_;
const uint64_t tags_hash_;
const bool is_tombstoned_;
const std::pair<uint16_t, uint16_t> modified_at_;
public: public:
/* Constructor used when adding new tracks to the database. */ TrackData()
TrackData(TrackId id, : id(0),
const std::pmr::string& path, filepath(&memory::kSpiRamResource),
uint64_t hash, tags_hash(0),
std::pair<uint16_t, uint16_t> modified_at) is_tombstoned(false),
: id_(id), modified_at() {}
filepath_(path, &memory::kSpiRamResource),
tags_hash_(hash), TrackId id;
is_tombstoned_(false), std::pmr::string filepath;
modified_at_(modified_at) {} uint64_t tags_hash;
bool is_tombstoned;
TrackData(TrackId id, std::pair<uint16_t, uint16_t> modified_at;
const std::pmr::string& path,
uint64_t hash,
bool is_tombstoned,
std::pair<uint16_t, uint16_t> modified_at)
: id_(id),
filepath_(path, &memory::kSpiRamResource),
tags_hash_(hash),
is_tombstoned_(is_tombstoned),
modified_at_(modified_at) {}
TrackData(TrackData&& other) = delete; TrackData(TrackData&& other) = delete;
TrackData& operator=(TrackData& other) = delete; TrackData& operator=(TrackData& other) = delete;
bool operator==(const TrackData&) const = default; bool operator==(const TrackData&) const = default;
auto id() const -> TrackId { return id_; }
auto filepath() const -> std::pmr::string { return filepath_; }
auto tags_hash() const -> uint64_t { return tags_hash_; }
auto is_tombstoned() const -> bool { return is_tombstoned_; }
auto modified_at() const -> std::pair<uint16_t, uint16_t> {
return modified_at_;
}
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::pmr::string& new_path) const -> TrackData;
auto UpdateModifiedAt(const std::pair<uint16_t, uint16_t>&) const
-> TrackData;
}; };
/* /*
@ -199,7 +158,7 @@ class Track {
auto TitleOrFilename() const -> std::pmr::string; auto TitleOrFilename() const -> std::pmr::string;
private: private:
std::shared_ptr<TrackData> data_; std::shared_ptr<const TrackData> data_;
std::shared_ptr<TrackTags> tags_; std::shared_ptr<TrackTags> tags_;
}; };

@ -89,7 +89,7 @@ auto Index(const IndexInfo& info, const Track& t, leveldb::WriteBatch* batch)
// If this is the last component, then we should also fill in the track id // If this is the last component, then we should also fill in the track id
// and title. // and title.
if (i == info.components.size() - 1) { if (i == info.components.size() - 1) {
key.track = t.data().id(); key.track = t.data().id;
value = t.TitleOrFilename(); value = t.TitleOrFilename();
} }

@ -63,12 +63,12 @@ auto EncodeDataKey(const TrackId& id) -> std::string {
auto EncodeDataValue(const TrackData& track) -> std::string { auto EncodeDataValue(const TrackData& track) -> std::string {
cppbor::Array val{ cppbor::Array val{
cppbor::Uint{track.id()}, cppbor::Uint{track.id},
cppbor::Tstr{track.filepath()}, cppbor::Tstr{track.filepath},
cppbor::Uint{track.tags_hash()}, cppbor::Uint{track.tags_hash},
cppbor::Bool{track.is_tombstoned()}, cppbor::Bool{track.is_tombstoned},
cppbor::Uint{track.modified_at().first}, cppbor::Uint{track.modified_at.first},
cppbor::Uint{track.modified_at().second}, cppbor::Uint{track.modified_at.second},
}; };
return val.toString(); return val.toString();
} }
@ -88,16 +88,15 @@ auto ParseDataValue(const leveldb::Slice& slice) -> std::shared_ptr<TrackData> {
vals->get(5)->type() != cppbor::UINT) { vals->get(5)->type() != cppbor::UINT) {
return {}; return {};
} }
TrackId id = vals->get(0)->asUint()->unsignedValue(); auto res = std::make_shared<TrackData>();
auto path = vals->get(1)->asViewTstr()->view(); res->id = vals->get(0)->asUint()->unsignedValue();
uint64_t hash = vals->get(2)->asUint()->unsignedValue(); res->filepath = vals->get(1)->asViewTstr()->view();
bool tombstoned = vals->get(3)->asBool()->value(); res->tags_hash = vals->get(2)->asUint()->unsignedValue();
auto modified_at = std::make_pair<uint16_t, uint16_t>( res->is_tombstoned = vals->get(3)->asBool()->value();
res->modified_at = std::make_pair<uint16_t, uint16_t>(
vals->get(4)->asUint()->unsignedValue(), vals->get(4)->asUint()->unsignedValue(),
vals->get(5)->asUint()->unsignedValue()); vals->get(5)->asUint()->unsignedValue());
return std::make_shared<TrackData>(id, return res;
std::pmr::string{path.data(), path.size()},
hash, tombstoned, modified_at);
} }
/* 'H/ 0xBEEF' */ /* 'H/ 0xBEEF' */

@ -53,33 +53,16 @@ auto TrackTags::Hash() const -> uint64_t {
return komihash_stream_final(&stream); return komihash_stream_final(&stream);
} }
auto TrackData::UpdateHash(uint64_t new_hash) const -> TrackData {
return TrackData(id_, filepath_, new_hash, is_tombstoned_, modified_at_);
}
auto TrackData::Entomb() const -> TrackData {
return TrackData(id_, filepath_, tags_hash_, true, modified_at_);
}
auto TrackData::Exhume(const std::pmr::string& new_path) const -> TrackData {
return TrackData(id_, new_path, tags_hash_, false, modified_at_);
}
auto TrackData::UpdateModifiedAt(
const std::pair<uint16_t, uint16_t>& new_time) const -> TrackData {
return TrackData(id_, filepath_, tags_hash_, false, new_time);
}
auto Track::TitleOrFilename() const -> std::pmr::string { auto Track::TitleOrFilename() const -> std::pmr::string {
auto title = tags().at(Tag::kTitle); auto title = tags().at(Tag::kTitle);
if (title) { if (title) {
return *title; return *title;
} }
auto start = data().filepath().find_last_of('/'); auto start = data().filepath.find_last_of('/');
if (start == std::pmr::string::npos) { if (start == std::pmr::string::npos) {
return data().filepath(); return data().filepath;
} }
return data().filepath().substr(start); return data().filepath.substr(start);
} }
} // namespace database } // namespace database

@ -173,7 +173,7 @@ Playing::Playing(models::TopBar& top_bar_model,
if (!id) { if (!id) {
return; return;
} }
if (current_track_.get() && current_track_.get()->data().id() == *id) { if (current_track_.get() && current_track_.get()->data().id == *id) {
return; return;
} }
auto db = db_.lock(); auto db = db_.lock();

Loading…
Cancel
Save