diff options
Diffstat (limited to 'src/database')
| -rw-r--r-- | src/database/database.cpp | 56 | ||||
| -rw-r--r-- | src/database/include/database.hpp | 9 | ||||
| -rw-r--r-- | src/database/include/future_fetcher.hpp | 62 | ||||
| -rw-r--r-- | src/database/include/track.hpp | 3 | ||||
| -rw-r--r-- | src/database/tag_parser.cpp | 3 |
5 files changed, 133 insertions, 0 deletions
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<std::optional<Track>> { + return worker_task_->Dispatch<std::optional<Track>>( + [=, this]() -> std::optional<Track> { + std::optional<TrackData> 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<TrackId> ids) + -> std::future<std::vector<std::optional<Track>>> { + return worker_task_->Dispatch<std::vector<std::optional<Track>>>( + [=, this]() -> std::vector<std::optional<Track>> { + std::map<TrackId, Track> 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<TrackId> 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> track = + ParseRecord<Track>(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<std::optional<Track>> 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<IndexInfo> { // 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<std::optional<std::string>>; + auto GetTrack(TrackId id) -> std::future<std::optional<Track>>; + + /* + * Fetches data for multiple tracks more efficiently than multiple calls to + * GetTrack. + */ + auto GetBulkTracks(std::vector<TrackId> id) + -> std::future<std::vector<std::optional<Track>>>; + auto GetIndexes() -> std::vector<IndexInfo>; auto GetTracksByIndex(const IndexInfo& index, std::size_t page_size) -> std::future<Result<IndexRecord>*>; 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 <me@jacqueline.id.au> + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include <memory> +#include <utility> + +#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 <typename T> +class FutureFetcher { + public: + explicit FutureFetcher(std::future<T>&& 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<T> { + assert(!is_consumed_); + if (is_consumed_) { + return {}; + } + is_consumed_ = true; + if (!fut_.valid()) { + return {}; + } + return fut_.get(); + } + + private: + bool is_consumed_; + std::future<T> fut_; +}; + +} // namespace database diff --git a/src/database/include/track.hpp b/src/database/include/track.hpp index e3f94db4..620fc59e 100644 --- a/src/database/include/track.hpp +++ b/src/database/include/track.hpp @@ -50,6 +50,7 @@ enum class Tag { kAlbum = 2, kAlbumTrack = 3, kGenre = 4, + kDuration = 5, }; /* @@ -67,6 +68,8 @@ class TrackTags { std::optional<int> sample_rate; std::optional<int> bits_per_sample; + std::optional<int> duration; + auto set(const Tag& key, const std::string& val) -> void; auto at(const Tag& key) const -> std::optional<shared_string>; auto operator[](const Tag& key) const -> std::optional<shared_string>; 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; } |
