Do not attempt extra data fetch when hitting the end of a query
This commit is contained in:
@@ -343,6 +343,14 @@ namespace lms::db
|
||||
utils::execQuery(query, params.range, func);
|
||||
}
|
||||
|
||||
void Track::find(Session& session, const FindParameters& params, bool& moreResults, std::function<void(const Track::pointer&)> func)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
auto query{ createQuery<Wt::Dbo::ptr<Track>>(session, params)};
|
||||
utils::execQuery(query, params.range, moreResults, func);
|
||||
}
|
||||
|
||||
RangeResults<TrackId> Track::findSimilarTrackIds(Session& session, const std::vector<TrackId>& tracks, std::optional<Range> range)
|
||||
{
|
||||
assert(!tracks.empty());
|
||||
|
||||
@@ -142,25 +142,21 @@ namespace lms::db
|
||||
TrackListEntry::pointer res;
|
||||
|
||||
auto entries = getEntries(Range{ pos, 1 });
|
||||
if (!entries.empty())
|
||||
res = entries.front();
|
||||
if (!entries.results.empty())
|
||||
res = entries.results.front();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<TrackListEntry::pointer> TrackList::getEntries(std::optional<Range> range) const
|
||||
RangeResults<ObjectPtr<TrackListEntry>> TrackList::getEntries(std::optional<Range> range) const
|
||||
{
|
||||
assert(session());
|
||||
|
||||
auto entries{
|
||||
session()->find<TrackListEntry>()
|
||||
auto query{session()->find<TrackListEntry>()
|
||||
.where("tracklist_id = ?").bind(getId())
|
||||
.orderBy("id")
|
||||
.limit(range ? static_cast<int>(range->size) + 1 : -1)
|
||||
.offset(range ? static_cast<int>(range->offset) : -1)
|
||||
.resultList() };
|
||||
.orderBy("id") };
|
||||
|
||||
return std::vector<TrackListEntry::pointer>(entries.begin(), entries.end());
|
||||
return utils::execQuery<TrackListEntry::pointer>(query, range);
|
||||
}
|
||||
|
||||
TrackListEntry::pointer TrackList::getEntryByTrackAndDateTime(ObjectPtr<Track> track, const Wt::WDateTime& dateTime) const
|
||||
@@ -237,17 +233,6 @@ namespace lms::db
|
||||
return res;
|
||||
}
|
||||
|
||||
bool TrackList::hasTrack(TrackId trackId) const
|
||||
{
|
||||
assert(session());
|
||||
|
||||
Wt::Dbo::collection<TrackListEntry::pointer> res = session()->query<TrackListEntry::pointer>("SELECT p_e from tracklist_entry p_e INNER JOIN tracklist p ON p_e.tracklist_id = p.id")
|
||||
.where("p_e.track_id = ?").bind(trackId)
|
||||
.where("p.id = ?").bind(getId());
|
||||
|
||||
return res.size() > 0;
|
||||
}
|
||||
|
||||
std::vector<Track::pointer> TrackList::getSimilarTracks(std::optional<std::size_t> offset, std::optional<std::size_t> size) const
|
||||
{
|
||||
assert(session());
|
||||
|
||||
@@ -56,15 +56,20 @@ namespace lms::db::utils
|
||||
{
|
||||
res.range.offset = range->offset;
|
||||
applyRange(query, Range{ range->offset, range->size + 1 });
|
||||
|
||||
res.results.reserve(range->size);
|
||||
}
|
||||
|
||||
auto collection{ query.resultList() };
|
||||
res.results.assign(collection.begin(), collection.end());
|
||||
if (range && res.results.size() == static_cast<std::size_t>(range->size) + 1)
|
||||
for (auto itResult{ collection.begin() }; itResult != collection.end(); ++itResult)
|
||||
{
|
||||
if (range && res.results.size() == range->size)
|
||||
{
|
||||
// TODO may optim by not actually requesting the last one
|
||||
res.moreResults = true;
|
||||
res.results.pop_back();
|
||||
break;
|
||||
}
|
||||
|
||||
res.results.push_back(std::move(*itResult));
|
||||
}
|
||||
|
||||
res.range.size = res.results.size();
|
||||
@@ -85,6 +90,28 @@ namespace lms::db::utils
|
||||
}
|
||||
}
|
||||
|
||||
template <typename ResultType, typename Query>
|
||||
void execQuery(Query& query, std::optional<Range> range, bool& moreResults, std::function<void(const ResultType&)> func)
|
||||
{
|
||||
if (range)
|
||||
applyRange(query, Range{ range->offset, range->size + 1 });
|
||||
|
||||
moreResults = false;
|
||||
|
||||
std::size_t count{};
|
||||
for (const auto& res : query.resultList())
|
||||
{
|
||||
if (range && (count++ == static_cast<std::size_t>(range->size)))
|
||||
{
|
||||
moreResults = true;
|
||||
break;
|
||||
}
|
||||
|
||||
LMS_SCOPED_TRACE_DETAILED("Database", "ExecQueryResult");
|
||||
func(res);
|
||||
}
|
||||
}
|
||||
|
||||
Wt::WDateTime normalizeDateTime(const Wt::WDateTime& dateTime);
|
||||
}
|
||||
|
||||
|
||||
@@ -118,6 +118,7 @@ namespace lms::db
|
||||
static RangeResults<TrackId> findIds(Session& session, const FindParameters& parameters);
|
||||
static RangeResults<pointer> find(Session& session, const FindParameters& parameters);
|
||||
static void find(Session& session, const FindParameters& parameters, std::function<void(const Track::pointer&)> func);
|
||||
static void find(Session& session, const FindParameters& parameters, bool& moreResults, std::function<void(const Track::pointer&)> func);
|
||||
static RangeResults<PathResult> findPaths(Session& session, std::optional<Range> range = std::nullopt);
|
||||
static RangeResults<TrackId> findIdsTrackMBIDDuplicates(Session& session, std::optional<Range> range = std::nullopt);
|
||||
static RangeResults<TrackId> findIdsWithRecordingMBIDAndMissingFeatures(Session& session, std::optional<Range> range = std::nullopt);
|
||||
|
||||
@@ -85,7 +85,7 @@ namespace lms::db
|
||||
bool isEmpty() const;
|
||||
std::size_t getCount() const;
|
||||
ObjectPtr<TrackListEntry> getEntry(std::size_t pos) const;
|
||||
std::vector<ObjectPtr<TrackListEntry>> getEntries(std::optional<Range> range = {}) const;
|
||||
RangeResults<ObjectPtr<TrackListEntry>> getEntries(std::optional<Range> range = {}) const;
|
||||
ObjectPtr<TrackListEntry> getEntryByTrackAndDateTime(ObjectPtr<Track> track, const Wt::WDateTime& dateTime) const;
|
||||
|
||||
std::vector<TrackId> getTrackIds() const;
|
||||
@@ -97,8 +97,6 @@ namespace lms::db
|
||||
std::vector<ObjectPtr<Cluster>> getClusters() const;
|
||||
std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(const std::vector<ClusterTypeId>& clusterTypeIds, std::size_t size) const;
|
||||
|
||||
bool hasTrack(TrackId trackId) const;
|
||||
|
||||
// Ordered from most clusters in common
|
||||
std::vector<ObjectPtr<Track>> getSimilarTracks(std::optional<std::size_t> offset = {}, std::optional<std::size_t> size = {}) const;
|
||||
|
||||
|
||||
@@ -198,8 +198,8 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
auto entries{ trackList.get()->getEntries() };
|
||||
ASSERT_EQ(entries.size(), 1);
|
||||
EXPECT_EQ(entries.front()->getTrack()->getId(), track1.getId());
|
||||
ASSERT_EQ(entries.results.size(), 1);
|
||||
EXPECT_EQ(entries.results.front()->getTrack()->getId(), track1.getId());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -210,16 +210,16 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
auto entries{ trackList.get()->getEntries() };
|
||||
ASSERT_EQ(entries.size(), 2);
|
||||
EXPECT_EQ(entries[0]->getTrack()->getId(), track1.getId());
|
||||
EXPECT_EQ(entries[1]->getTrack()->getId(), track2.getId());
|
||||
ASSERT_EQ(entries.results.size(), 2);
|
||||
EXPECT_EQ(entries.results[0]->getTrack()->getId(), track1.getId());
|
||||
EXPECT_EQ(entries.results[1]->getTrack()->getId(), track2.getId());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
auto entries{ trackList.get()->getEntries(Range {1, 1}) };
|
||||
ASSERT_EQ(entries.size(), 1);
|
||||
EXPECT_EQ(entries[0]->getTrack()->getId(), track2.getId());
|
||||
ASSERT_EQ(entries.results.size(), 1);
|
||||
EXPECT_EQ(entries.results[0]->getTrack()->getId(), track2.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,7 @@ namespace lms::api::subsonic
|
||||
Response::Node playlistNode{ createPlaylistNode(tracklist, context.dbSession) };
|
||||
|
||||
auto entries{ tracklist->getEntries() };
|
||||
for (const TrackListEntry::pointer& entry : entries)
|
||||
for (const TrackListEntry::pointer& entry : entries.results)
|
||||
playlistNode.addArrayChild("entry", createSongNode(context, entry->getTrack(), user));
|
||||
|
||||
response.addNode("playlist", std::move(playlistNode));
|
||||
@@ -129,7 +129,7 @@ namespace lms::api::subsonic
|
||||
Response::Node playlistNode{ createPlaylistNode(tracklist, context.dbSession) };
|
||||
|
||||
auto entries{ tracklist->getEntries() };
|
||||
for (const TrackListEntry::pointer& entry : entries)
|
||||
for (const TrackListEntry::pointer& entry : entries.results)
|
||||
playlistNode.addArrayChild("entry", createSongNode(context, entry->getTrack(), user));
|
||||
|
||||
response.addNode("playlist", std::move(playlistNode));
|
||||
|
||||
@@ -148,10 +148,11 @@ namespace lms::ui
|
||||
shuffleBtn->clicked().connect([this]
|
||||
{
|
||||
{
|
||||
// TODO write scope could be reduced
|
||||
auto transaction{ LmsApp->getDbSession().createWriteTransaction() };
|
||||
|
||||
db::TrackList::pointer queue{ getQueue() };
|
||||
auto entries{ queue->getEntries() };
|
||||
auto entries{ queue->getEntries().results };
|
||||
core::random::shuffleContainer(entries);
|
||||
|
||||
queue.modify()->clear();
|
||||
@@ -430,9 +431,9 @@ namespace lms::ui
|
||||
auto transaction{ LmsApp->getDbSession().createWriteTransaction() };
|
||||
|
||||
db::TrackList::pointer queue{ getQueue() };
|
||||
std::vector<db::TrackListEntry::pointer> entries{ queue->getEntries(db::Range {_trackPos ? *_trackPos + 1 : 0, getCapacity()}) };
|
||||
tracks.reserve(entries.size());
|
||||
for (db::TrackListEntry::pointer entry : entries)
|
||||
auto entries{ queue->getEntries(db::Range {_trackPos ? *_trackPos + 1 : 0, getCapacity()}) };
|
||||
tracks.reserve(entries.results.size());
|
||||
for (db::TrackListEntry::pointer& entry : entries.results)
|
||||
{
|
||||
tracks.push_back(entry->getTrack()->getId());
|
||||
entry.remove();
|
||||
@@ -492,9 +493,11 @@ namespace lms::ui
|
||||
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
|
||||
|
||||
const db::TrackList::pointer queue{ getQueue() };
|
||||
const auto tracklistEntries{ queue->getEntries(db::Range {_entriesContainer->getCount(), _batchSize}) };
|
||||
for (const db::TrackListEntry::pointer& tracklistEntry : tracklistEntries)
|
||||
const auto entries{ queue->getEntries(db::Range {_entriesContainer->getCount(), _batchSize}) };
|
||||
for (const db::TrackListEntry::pointer& tracklistEntry : entries.results)
|
||||
addEntry(tracklistEntry);
|
||||
|
||||
_entriesContainer->setHasMore(entries.moreResults);
|
||||
}
|
||||
|
||||
void PlayQueue::addEntry(const db::TrackListEntry::pointer& tracklistEntry)
|
||||
|
||||
@@ -32,39 +32,33 @@ namespace lms::ui
|
||||
reset();
|
||||
}
|
||||
|
||||
void
|
||||
InfiniteScrollingContainer::clear()
|
||||
void InfiniteScrollingContainer::clear()
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
|
||||
void
|
||||
InfiniteScrollingContainer::reset()
|
||||
void InfiniteScrollingContainer::reset()
|
||||
{
|
||||
_elements->clear();
|
||||
setHasMore(true);
|
||||
}
|
||||
|
||||
std::size_t
|
||||
InfiniteScrollingContainer::getCount()
|
||||
std::size_t InfiniteScrollingContainer::getCount()
|
||||
{
|
||||
return _elements->count();
|
||||
}
|
||||
|
||||
void
|
||||
InfiniteScrollingContainer::add(std::unique_ptr<Wt::WWidget> result)
|
||||
void InfiniteScrollingContainer::add(std::unique_ptr<Wt::WWidget> result)
|
||||
{
|
||||
return _elements->addWidget(std::move(result));
|
||||
}
|
||||
|
||||
void
|
||||
InfiniteScrollingContainer::setHasMore()
|
||||
void InfiniteScrollingContainer::setHasMore()
|
||||
{
|
||||
setHasMore(true);
|
||||
}
|
||||
|
||||
void
|
||||
InfiniteScrollingContainer::setHasMore(bool hasMore)
|
||||
void InfiniteScrollingContainer::setHasMore(bool hasMore)
|
||||
{
|
||||
if (hasMore)
|
||||
displayLoadingIndicator();
|
||||
@@ -72,21 +66,19 @@ namespace lms::ui
|
||||
hideLoadingIndicator();
|
||||
}
|
||||
|
||||
void
|
||||
InfiniteScrollingContainer::remove(Wt::WWidget& widget)
|
||||
void InfiniteScrollingContainer::remove(Wt::WWidget& widget)
|
||||
{
|
||||
_elements->removeWidget(&widget);
|
||||
}
|
||||
|
||||
void
|
||||
InfiniteScrollingContainer::remove(std::size_t first, std::size_t last)
|
||||
void InfiniteScrollingContainer::remove(std::size_t first, std::size_t last)
|
||||
{
|
||||
assert(last >= first);
|
||||
assert(last < static_cast<std::size_t>(_elements->count()));
|
||||
|
||||
// remove from end as API is quite uneffective (minimize moves)
|
||||
std::size_t i{ last };
|
||||
while (Wt::WWidget* widget {_elements->widget(i)})
|
||||
while (Wt::WWidget * widget{ _elements->widget(static_cast<int>(i)) })
|
||||
{
|
||||
_elements->removeWidget(widget);
|
||||
if (i-- == first)
|
||||
@@ -94,21 +86,18 @@ namespace lms::ui
|
||||
}
|
||||
}
|
||||
|
||||
Wt::WWidget*
|
||||
InfiniteScrollingContainer::getWidget(std::size_t pos) const
|
||||
Wt::WWidget* InfiniteScrollingContainer::getWidget(std::size_t pos) const
|
||||
{
|
||||
return _elements->widget(pos);
|
||||
return _elements->widget(static_cast<int>(pos));
|
||||
}
|
||||
|
||||
std::optional<std::size_t>
|
||||
InfiniteScrollingContainer::getIndexOf(Wt::WWidget& widget) const
|
||||
std::optional<std::size_t> InfiniteScrollingContainer::getIndexOf(Wt::WWidget& widget) const
|
||||
{
|
||||
return _elements->indexOf(&widget);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
InfiniteScrollingContainer::displayLoadingIndicator()
|
||||
void InfiniteScrollingContainer::displayLoadingIndicator()
|
||||
{
|
||||
_loadingIndicator = bindWidget<Wt::WTemplate>("loading-indicator", createLoadingIndicator());
|
||||
_loadingIndicator->scrollVisibilityChanged().connect([this](bool visible)
|
||||
@@ -116,17 +105,13 @@ namespace lms::ui
|
||||
if (!visible)
|
||||
return;
|
||||
|
||||
const auto previousCount {_elements->count()};
|
||||
onRequestElements.emit();
|
||||
setHasMore(previousCount != _elements->count());
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
InfiniteScrollingContainer::hideLoadingIndicator()
|
||||
void InfiniteScrollingContainer::hideLoadingIndicator()
|
||||
{
|
||||
_loadingIndicator = nullptr;
|
||||
bindEmpty("loading-indicator");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -57,11 +57,12 @@ namespace lms::ui
|
||||
|
||||
Wt::Signal<> onRequestElements;
|
||||
|
||||
void setHasMore(bool hasMore); // can be used to add elements afterwards
|
||||
|
||||
private:
|
||||
void clear() override;
|
||||
void displayLoadingIndicator();
|
||||
void hideLoadingIndicator();
|
||||
void setHasMore(bool hasMore); // can be used to add elements afterwards
|
||||
|
||||
Wt::WContainerWidget* _elements;
|
||||
Wt::WTemplate* _loadingIndicator;
|
||||
|
||||
@@ -21,8 +21,7 @@
|
||||
|
||||
namespace lms::ui
|
||||
{
|
||||
std::unique_ptr<Wt::WTemplate>
|
||||
createLoadingIndicator()
|
||||
std::unique_ptr<Wt::WTemplate> createLoadingIndicator()
|
||||
{
|
||||
auto res {std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.LoadingIndicator.template"))};
|
||||
|
||||
|
||||
@@ -332,12 +332,13 @@ namespace lms::ui
|
||||
releaseContainer.container->add(releaseListHelpers::createEntryForArtist(release, artist));
|
||||
}
|
||||
}
|
||||
|
||||
releaseContainer.container->setHasMore(false);
|
||||
}
|
||||
|
||||
bool Artist::addSomeNonReleaseTracks()
|
||||
{
|
||||
bool areTracksAdded{};
|
||||
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
|
||||
|
||||
const Range range{ static_cast<std::size_t>(_trackContainer->getCount()), _tracksBatchSize };
|
||||
|
||||
@@ -348,6 +349,8 @@ namespace lms::ui
|
||||
params.setSortMethod(TrackSortMethod::Name);
|
||||
params.setNonRelease(true);
|
||||
|
||||
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
|
||||
|
||||
const auto tracks{ Track::find(LmsApp->getDbSession(), params) };
|
||||
for (const Track::pointer& track : tracks.results)
|
||||
{
|
||||
@@ -360,6 +363,8 @@ namespace lms::ui
|
||||
areTracksAdded = true;
|
||||
}
|
||||
|
||||
_trackContainer->setHasMore(tracks.moreResults);
|
||||
|
||||
return areTracksAdded;
|
||||
}
|
||||
|
||||
|
||||
@@ -125,6 +125,8 @@ namespace lms::ui
|
||||
_container->add(ArtistListHelpers::createEntry(artist));
|
||||
}
|
||||
}
|
||||
|
||||
_container->setHasMore(artistIds.moreResults);
|
||||
}
|
||||
|
||||
} // namespace lms::ui
|
||||
|
||||
@@ -463,7 +463,7 @@ namespace lms::ui
|
||||
auto isStarred{ [=] { return core::Service<feedback::IFeedbackService>::get()->isStarred(LmsApp->getUserId(), trackId); } };
|
||||
|
||||
Wt::WPushButton* starBtn{ entry->bindNew<Wt::WPushButton>("star", Wt::WString::tr(isStarred() ? "Lms.Explore.unstar" : "Lms.Explore.star")) };
|
||||
starBtn->clicked().connect([=, this]
|
||||
starBtn->clicked().connect([=]
|
||||
{
|
||||
if (isStarred())
|
||||
{
|
||||
|
||||
@@ -129,6 +129,8 @@ namespace lms::ui
|
||||
_container->add(releaseListHelpers::createEntry(release));
|
||||
}
|
||||
}
|
||||
|
||||
_container->setHasMore(releaseIds.moreResults);
|
||||
}
|
||||
|
||||
std::vector<ReleaseId> Releases::getAllReleases()
|
||||
|
||||
@@ -159,6 +159,8 @@ namespace lms::ui
|
||||
_artists->add(ArtistListHelpers::createEntry(artist));
|
||||
}
|
||||
}
|
||||
|
||||
_artists->setHasMore(artistIds.moreResults);
|
||||
}
|
||||
|
||||
void SearchView::addSomeReleases()
|
||||
@@ -177,6 +179,8 @@ namespace lms::ui
|
||||
_releases->add(releaseListHelpers::createEntry(release));
|
||||
}
|
||||
}
|
||||
|
||||
_releases->setHasMore(releaseIds.moreResults);
|
||||
}
|
||||
|
||||
void SearchView::addSomeTracks()
|
||||
@@ -196,5 +200,7 @@ namespace lms::ui
|
||||
_tracks->add(TrackListHelpers::createEntry(track, _playQueueController, _filters));
|
||||
}
|
||||
}
|
||||
|
||||
_tracks->setHasMore(trackIds.moreResults);
|
||||
}
|
||||
}
|
||||
@@ -188,10 +188,13 @@ namespace lms::ui
|
||||
params.setSortMethod(db::TrackSortMethod::TrackList);
|
||||
params.setRange(db::Range{ static_cast<std::size_t>(_container->getCount()), _batchSize });
|
||||
|
||||
db::Track::find(LmsApp->getDbSession(), params, [this](const Track::pointer& track)
|
||||
bool moreResults{};
|
||||
db::Track::find(LmsApp->getDbSession(), params, moreResults, [this](const Track::pointer& track)
|
||||
{
|
||||
_container->add(TrackListHelpers::createEntry(track, _playQueueController, _filters));
|
||||
});
|
||||
|
||||
_container->setHasMore(moreResults);
|
||||
}
|
||||
} // namespace lms::ui
|
||||
|
||||
|
||||
@@ -121,6 +121,8 @@ namespace lms::ui
|
||||
if (const TrackList::pointer trackList{ TrackList::find(LmsApp->getDbSession(), trackListId) })
|
||||
addTracklist(trackList);
|
||||
}
|
||||
|
||||
_container->setHasMore(trackListIds.moreResults);
|
||||
}
|
||||
|
||||
void TrackLists::addTracklist(const ObjectPtr<TrackList>& trackList)
|
||||
|
||||
@@ -128,6 +128,8 @@ namespace lms::ui
|
||||
if (const Track::pointer track{ Track::find(LmsApp->getDbSession(), trackId) })
|
||||
_container->add(TrackListHelpers::createEntry(track, _playQueueController, _filters));
|
||||
}
|
||||
|
||||
_container->setHasMore(trackIds.moreResults);
|
||||
}
|
||||
|
||||
std::vector<db::TrackId> Tracks::getAllTracks()
|
||||
|
||||
Reference in New Issue
Block a user