diff --git a/src/libs/database/impl/Track.cpp b/src/libs/database/impl/Track.cpp index d9f718ff..25a5bd80 100644 --- a/src/libs/database/impl/Track.cpp +++ b/src/libs/database/impl/Track.cpp @@ -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 func) + { + session.checkReadTransaction(); + + auto query{ createQuery>(session, params)}; + utils::execQuery(query, params.range, moreResults, func); + } + RangeResults Track::findSimilarTrackIds(Session& session, const std::vector& tracks, std::optional range) { assert(!tracks.empty()); diff --git a/src/libs/database/impl/TrackList.cpp b/src/libs/database/impl/TrackList.cpp index e163970f..65820591 100644 --- a/src/libs/database/impl/TrackList.cpp +++ b/src/libs/database/impl/TrackList.cpp @@ -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 TrackList::getEntries(std::optional range) const + RangeResults> TrackList::getEntries(std::optional range) const { assert(session()); - auto entries{ - session()->find() + auto query{session()->find() .where("tracklist_id = ?").bind(getId()) - .orderBy("id") - .limit(range ? static_cast(range->size) + 1 : -1) - .offset(range ? static_cast(range->offset) : -1) - .resultList() }; + .orderBy("id") }; - return std::vector(entries.begin(), entries.end()); + return utils::execQuery(query, range); } TrackListEntry::pointer TrackList::getEntryByTrackAndDateTime(ObjectPtr 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 res = session()->query("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 TrackList::getSimilarTracks(std::optional offset, std::optional size) const { assert(session()); diff --git a/src/libs/database/impl/Utils.hpp b/src/libs/database/impl/Utils.hpp index b92f112e..8610104e 100644 --- a/src/libs/database/impl/Utils.hpp +++ b/src/libs/database/impl/Utils.hpp @@ -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(range->size) + 1) + for (auto itResult{ collection.begin() }; itResult != collection.end(); ++itResult) { - // TODO may optim by not actually requesting the last one - res.moreResults = true; - res.results.pop_back(); + if (range && res.results.size() == range->size) + { + res.moreResults = true; + break; + } + + res.results.push_back(std::move(*itResult)); } res.range.size = res.results.size(); @@ -85,6 +90,28 @@ namespace lms::db::utils } } + template + void execQuery(Query& query, std::optional range, bool& moreResults, std::function 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(range->size))) + { + moreResults = true; + break; + } + + LMS_SCOPED_TRACE_DETAILED("Database", "ExecQueryResult"); + func(res); + } + } + Wt::WDateTime normalizeDateTime(const Wt::WDateTime& dateTime); } diff --git a/src/libs/database/include/database/Track.hpp b/src/libs/database/include/database/Track.hpp index d3b3586d..68ac088d 100644 --- a/src/libs/database/include/database/Track.hpp +++ b/src/libs/database/include/database/Track.hpp @@ -118,6 +118,7 @@ namespace lms::db static RangeResults findIds(Session& session, const FindParameters& parameters); static RangeResults find(Session& session, const FindParameters& parameters); static void find(Session& session, const FindParameters& parameters, std::function func); + static void find(Session& session, const FindParameters& parameters, bool& moreResults, std::function func); static RangeResults findPaths(Session& session, std::optional range = std::nullopt); static RangeResults findIdsTrackMBIDDuplicates(Session& session, std::optional range = std::nullopt); static RangeResults findIdsWithRecordingMBIDAndMissingFeatures(Session& session, std::optional range = std::nullopt); diff --git a/src/libs/database/include/database/TrackList.hpp b/src/libs/database/include/database/TrackList.hpp index b961bb5e..bdeff6bb 100644 --- a/src/libs/database/include/database/TrackList.hpp +++ b/src/libs/database/include/database/TrackList.hpp @@ -85,7 +85,7 @@ namespace lms::db bool isEmpty() const; std::size_t getCount() const; ObjectPtr getEntry(std::size_t pos) const; - std::vector> getEntries(std::optional range = {}) const; + RangeResults> getEntries(std::optional range = {}) const; ObjectPtr getEntryByTrackAndDateTime(ObjectPtr track, const Wt::WDateTime& dateTime) const; std::vector getTrackIds() const; @@ -97,8 +97,6 @@ namespace lms::db std::vector> getClusters() const; std::vector>> getClusterGroups(const std::vector& clusterTypeIds, std::size_t size) const; - bool hasTrack(TrackId trackId) const; - // Ordered from most clusters in common std::vector> getSimilarTracks(std::optional offset = {}, std::optional size = {}) const; diff --git a/src/libs/database/test/TrackList.cpp b/src/libs/database/test/TrackList.cpp index d69fda7f..ee2e31d2 100644 --- a/src/libs/database/test/TrackList.cpp +++ b/src/libs/database/test/TrackList.cpp @@ -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()); } } } \ No newline at end of file diff --git a/src/libs/subsonic/impl/entrypoints/Playlists.cpp b/src/libs/subsonic/impl/entrypoints/Playlists.cpp index 57f20d0e..d27e7be3 100644 --- a/src/libs/subsonic/impl/entrypoints/Playlists.cpp +++ b/src/libs/subsonic/impl/entrypoints/Playlists.cpp @@ -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)); diff --git a/src/lms/ui/PlayQueue.cpp b/src/lms/ui/PlayQueue.cpp index 8e9e7045..d00c670f 100644 --- a/src/lms/ui/PlayQueue.cpp +++ b/src/lms/ui/PlayQueue.cpp @@ -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 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) diff --git a/src/lms/ui/common/InfiniteScrollingContainer.cpp b/src/lms/ui/common/InfiniteScrollingContainer.cpp index 802e2894..6f33dd22 100644 --- a/src/lms/ui/common/InfiniteScrollingContainer.cpp +++ b/src/lms/ui/common/InfiniteScrollingContainer.cpp @@ -24,109 +24,94 @@ namespace lms::ui { - InfiniteScrollingContainer::InfiniteScrollingContainer(const Wt::WString& text) - : Wt::WTemplate {text} - , _elements {bindNew("elements")} - , _loadingIndicator {bindWidget("loading-indicator", createLoadingIndicator())} - { - reset(); - } + InfiniteScrollingContainer::InfiniteScrollingContainer(const Wt::WString& text) + : Wt::WTemplate{ text } + , _elements{ bindNew("elements") } + , _loadingIndicator{ bindWidget("loading-indicator", createLoadingIndicator()) } + { + reset(); + } - void - InfiniteScrollingContainer::clear() - { - assert(false); - } + void InfiniteScrollingContainer::clear() + { + assert(false); + } - void - InfiniteScrollingContainer::reset() - { - _elements->clear(); - setHasMore(true); - } + void InfiniteScrollingContainer::reset() + { + _elements->clear(); + setHasMore(true); + } - std::size_t - InfiniteScrollingContainer::getCount() - { - return _elements->count(); - } + std::size_t InfiniteScrollingContainer::getCount() + { + return _elements->count(); + } - void - InfiniteScrollingContainer::add(std::unique_ptr result) - { - return _elements->addWidget(std::move(result)); - } + void InfiniteScrollingContainer::add(std::unique_ptr result) + { + return _elements->addWidget(std::move(result)); + } - void - InfiniteScrollingContainer::setHasMore() - { - setHasMore(true); - } + void InfiniteScrollingContainer::setHasMore() + { + setHasMore(true); + } - void - InfiniteScrollingContainer::setHasMore(bool hasMore) - { - if (hasMore) - displayLoadingIndicator(); - else - hideLoadingIndicator(); - } + void InfiniteScrollingContainer::setHasMore(bool hasMore) + { + if (hasMore) + displayLoadingIndicator(); + else + hideLoadingIndicator(); + } - void - InfiniteScrollingContainer::remove(Wt::WWidget& widget) - { - _elements->removeWidget(&widget); - } + void InfiniteScrollingContainer::remove(Wt::WWidget& widget) + { + _elements->removeWidget(&widget); + } - void - InfiniteScrollingContainer::remove(std::size_t first, std::size_t last) - { - assert(last >= first); - assert(last < static_cast(_elements->count())); + void InfiniteScrollingContainer::remove(std::size_t first, std::size_t last) + { + assert(last >= first); + assert(last < static_cast(_elements->count())); - // remove from end as API is quite uneffective (minimize moves) - std::size_t i {last}; - while (Wt::WWidget* widget {_elements->widget(i)}) - { - _elements->removeWidget(widget); - if (i-- == first) - break; - } - } + // remove from end as API is quite uneffective (minimize moves) + std::size_t i{ last }; + while (Wt::WWidget * widget{ _elements->widget(static_cast(i)) }) + { + _elements->removeWidget(widget); + if (i-- == first) + break; + } + } - Wt::WWidget* - InfiniteScrollingContainer::getWidget(std::size_t pos) const - { - return _elements->widget(pos); - } + Wt::WWidget* InfiniteScrollingContainer::getWidget(std::size_t pos) const + { + return _elements->widget(static_cast(pos)); + } - std::optional - InfiniteScrollingContainer::getIndexOf(Wt::WWidget& widget) const - { - return _elements->indexOf(&widget); - } + std::optional InfiniteScrollingContainer::getIndexOf(Wt::WWidget& widget) const + { + return _elements->indexOf(&widget); + } - void - InfiniteScrollingContainer::displayLoadingIndicator() - { - _loadingIndicator = bindWidget("loading-indicator", createLoadingIndicator()); - _loadingIndicator->scrollVisibilityChanged().connect([this](bool visible) - { - if (!visible) - return; + void InfiniteScrollingContainer::displayLoadingIndicator() + { + _loadingIndicator = bindWidget("loading-indicator", createLoadingIndicator()); + _loadingIndicator->scrollVisibilityChanged().connect([this](bool visible) + { + if (!visible) + return; - const auto previousCount {_elements->count()}; - onRequestElements.emit(); - setHasMore(previousCount != _elements->count()); - }); - } - - void - InfiniteScrollingContainer::hideLoadingIndicator() - { - _loadingIndicator = nullptr; - bindEmpty("loading-indicator"); - } + onRequestElements.emit(); + }); + } + void InfiniteScrollingContainer::hideLoadingIndicator() + { + _loadingIndicator = nullptr; + bindEmpty("loading-indicator"); + } } diff --git a/src/lms/ui/common/InfiniteScrollingContainer.hpp b/src/lms/ui/common/InfiniteScrollingContainer.hpp index 80cbc04d..d2fa6888 100644 --- a/src/lms/ui/common/InfiniteScrollingContainer.hpp +++ b/src/lms/ui/common/InfiniteScrollingContainer.hpp @@ -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; diff --git a/src/lms/ui/common/LoadingIndicator.cpp b/src/lms/ui/common/LoadingIndicator.cpp index 5ddd8b4e..0912f4b6 100644 --- a/src/lms/ui/common/LoadingIndicator.cpp +++ b/src/lms/ui/common/LoadingIndicator.cpp @@ -21,16 +21,15 @@ namespace lms::ui { - std::unique_ptr - createLoadingIndicator() - { - auto res {std::make_unique(Wt::WString::tr("Lms.LoadingIndicator.template"))}; + std::unique_ptr createLoadingIndicator() + { + auto res {std::make_unique(Wt::WString::tr("Lms.LoadingIndicator.template"))}; - res->addFunction("tr", &Wt::WTemplate::Functions::tr); - res->setScrollVisibilityEnabled(true); - res->setScrollVisibilityMargin(200); + res->addFunction("tr", &Wt::WTemplate::Functions::tr); + res->setScrollVisibilityEnabled(true); + res->setScrollVisibilityMargin(200); - return res; - } + return res; + } } diff --git a/src/lms/ui/explore/ArtistView.cpp b/src/lms/ui/explore/ArtistView.cpp index c21fd77d..f8e31405 100644 --- a/src/lms/ui/explore/ArtistView.cpp +++ b/src/lms/ui/explore/ArtistView.cpp @@ -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(_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; } diff --git a/src/lms/ui/explore/ArtistsView.cpp b/src/lms/ui/explore/ArtistsView.cpp index 1f7cf1ce..589b9966 100644 --- a/src/lms/ui/explore/ArtistsView.cpp +++ b/src/lms/ui/explore/ArtistsView.cpp @@ -125,6 +125,8 @@ namespace lms::ui _container->add(ArtistListHelpers::createEntry(artist)); } } + + _container->setHasMore(artistIds.moreResults); } } // namespace lms::ui diff --git a/src/lms/ui/explore/ReleaseView.cpp b/src/lms/ui/explore/ReleaseView.cpp index 5b512571..841cd234 100644 --- a/src/lms/ui/explore/ReleaseView.cpp +++ b/src/lms/ui/explore/ReleaseView.cpp @@ -463,7 +463,7 @@ namespace lms::ui auto isStarred{ [=] { return core::Service::get()->isStarred(LmsApp->getUserId(), trackId); } }; Wt::WPushButton* starBtn{ entry->bindNew("star", Wt::WString::tr(isStarred() ? "Lms.Explore.unstar" : "Lms.Explore.star")) }; - starBtn->clicked().connect([=, this] + starBtn->clicked().connect([=] { if (isStarred()) { diff --git a/src/lms/ui/explore/ReleasesView.cpp b/src/lms/ui/explore/ReleasesView.cpp index a38ae4cf..82055427 100644 --- a/src/lms/ui/explore/ReleasesView.cpp +++ b/src/lms/ui/explore/ReleasesView.cpp @@ -129,6 +129,8 @@ namespace lms::ui _container->add(releaseListHelpers::createEntry(release)); } } + + _container->setHasMore(releaseIds.moreResults); } std::vector Releases::getAllReleases() diff --git a/src/lms/ui/explore/SearchView.cpp b/src/lms/ui/explore/SearchView.cpp index ca99dd96..00f690f5 100644 --- a/src/lms/ui/explore/SearchView.cpp +++ b/src/lms/ui/explore/SearchView.cpp @@ -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); } } \ No newline at end of file diff --git a/src/lms/ui/explore/TrackListView.cpp b/src/lms/ui/explore/TrackListView.cpp index b4852f76..4fd379d3 100644 --- a/src/lms/ui/explore/TrackListView.cpp +++ b/src/lms/ui/explore/TrackListView.cpp @@ -187,11 +187,14 @@ namespace lms::ui params.setTrackList(_trackListId); params.setSortMethod(db::TrackSortMethod::TrackList); params.setRange(db::Range{ static_cast(_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 diff --git a/src/lms/ui/explore/TrackListsView.cpp b/src/lms/ui/explore/TrackListsView.cpp index 5598c7cb..fb023f35 100644 --- a/src/lms/ui/explore/TrackListsView.cpp +++ b/src/lms/ui/explore/TrackListsView.cpp @@ -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) diff --git a/src/lms/ui/explore/TracksView.cpp b/src/lms/ui/explore/TracksView.cpp index b86c0432..9274951a 100644 --- a/src/lms/ui/explore/TracksView.cpp +++ b/src/lms/ui/explore/TracksView.cpp @@ -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 Tracks::getAllTracks()