From 39f7545cd5ef7a30bbd482f3579df7744c6b688d Mon Sep 17 00:00:00 2001 From: jacqueline Date: Fri, 7 Jul 2023 15:29:47 +1000 Subject: wire up the playing screen with some real data Includes implementing song duration calculation for CBR MP3 files --- src/database/database.cpp | 56 +++++++++++++++++++++++++++++ src/database/include/database.hpp | 9 +++++ src/database/include/future_fetcher.hpp | 62 +++++++++++++++++++++++++++++++++ src/database/include/track.hpp | 2 ++ src/database/tag_parser.cpp | 3 ++ 5 files changed, 132 insertions(+) create mode 100644 src/database/include/future_fetcher.hpp (limited to 'src/database') diff --git a/src/database/database.cpp b/src/database/database.cpp index 1ac5d729..0d1c43e2 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -268,6 +268,62 @@ auto Database::GetTrackPath(TrackId id) }); } +auto Database::GetTrack(TrackId id) -> std::future> { + return worker_task_->Dispatch>( + [=, this]() -> std::optional { + std::optional data = dbGetTrackData(id); + if (!data || data->is_tombstoned()) { + return {}; + } + TrackTags tags; + if (!tag_parser_->ReadAndParseTags(data->filepath(), &tags)) { + return {}; + } + return Track(*data, tags); + }); +} + +auto Database::GetBulkTracks(std::vector ids) + -> std::future>> { + return worker_task_->Dispatch>>( + [=, this]() -> std::vector> { + std::map id_to_track{}; + + // Sort the list of ids so that we can retrieve them all in a single + // iteration through the database, without re-seeking. + std::vector sorted_ids = ids; + std::sort(sorted_ids.begin(), sorted_ids.end()); + + leveldb::Iterator* it = db_->NewIterator(leveldb::ReadOptions{}); + for (const TrackId& id : sorted_ids) { + OwningSlice key = EncodeDataKey(id); + it->Seek(key.slice); + if (!it->Valid() || it->key() != key.slice) { + // This id wasn't found at all. Skip it. + continue; + } + std::optional track = + ParseRecord(it->key(), it->value()); + if (track) { + id_to_track.insert({id, *track}); + } + } + + // We've fetched all of the ids in the request, so now just put them + // back into the order they were asked for in. + std::vector> results; + for (const TrackId& id : ids) { + if (id_to_track.contains(id)) { + results.push_back(id_to_track.at(id)); + } else { + // This lookup failed. + results.push_back({}); + } + } + return results; + }); +} + auto Database::GetIndexes() -> std::vector { // TODO(jacqueline): This probably needs to be async? When we have runtime // configurable indexes, they will need to come from somewhere. diff --git a/src/database/include/database.hpp b/src/database/include/database.hpp index 77a17b75..7ffc15b0 100644 --- a/src/database/include/database.hpp +++ b/src/database/include/database.hpp @@ -100,6 +100,15 @@ class Database { auto GetTrackPath(TrackId id) -> std::future>; + auto GetTrack(TrackId id) -> std::future>; + + /* + * Fetches data for multiple tracks more efficiently than multiple calls to + * GetTrack. + */ + auto GetBulkTracks(std::vector id) + -> std::future>>; + auto GetIndexes() -> std::vector; auto GetTracksByIndex(const IndexInfo& index, std::size_t page_size) -> std::future*>; diff --git a/src/database/include/future_fetcher.hpp b/src/database/include/future_fetcher.hpp new file mode 100644 index 00000000..e8ce9729 --- /dev/null +++ b/src/database/include/future_fetcher.hpp @@ -0,0 +1,62 @@ +/* + * Copyright 2023 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include +#include + +#include "database.hpp" + +namespace database { + +/* + * Utility to simplify waiting for a std::future to complete without blocking. + * Each instance is good for a single future, and does not directly own anything + * other than the future itself. + */ +template +class FutureFetcher { + public: + explicit FutureFetcher(std::future&& fut) + : is_consumed_(false), fut_(std::move(fut)) {} + + /* + * Returns whether or not the underlying future is still awaiting async work. + */ + auto Finished() -> bool { + if (!fut_.valid()) { + return true; + } + if (fut_.wait_for(std::chrono::seconds(0)) != std::future_status::ready) { + return false; + } + return true; + } + + /* + * Returns the result of the future, and releases ownership of the underling + * resource. Will return an absent value if the future became invalid (e.g. + * the promise associated with it was destroyed.) + */ + auto Result() -> std::optional { + assert(!is_consumed_); + if (is_consumed_) { + return {}; + } + is_consumed_ = true; + if (!fut_.valid()) { + return {}; + } + return fut_.get(); + } + + private: + bool is_consumed_; + std::future fut_; +}; + +} // namespace database diff --git a/src/database/include/track.hpp b/src/database/include/track.hpp index 87fae9b9..620fc59e 100644 --- a/src/database/include/track.hpp +++ b/src/database/include/track.hpp @@ -68,6 +68,8 @@ class TrackTags { std::optional sample_rate; std::optional bits_per_sample; + std::optional duration; + auto set(const Tag& key, const std::string& val) -> void; auto at(const Tag& key) const -> std::optional; auto operator[](const Tag& key) const -> std::optional; diff --git a/src/database/tag_parser.cpp b/src/database/tag_parser.cpp index b2d206d2..2b784ea5 100644 --- a/src/database/tag_parser.cpp +++ b/src/database/tag_parser.cpp @@ -156,6 +156,9 @@ auto TagParserImpl::ReadAndParseTags(const std::string& path, TrackTags* out) if (ctx.bitrate > 0) { out->bits_per_sample = ctx.bitrate; } + if (ctx.duration > 0) { + out->duration = ctx.duration; + } return true; } -- cgit v1.2.3