diff --git a/approot/messages.xml b/approot/messages.xml index d60cd82c..dad967c1 100644 --- a/approot/messages.xml +++ b/approot/messages.xml @@ -128,8 +128,11 @@ Recently added Recently played Albums +Star +Starred Tracks Type +Unstar Value Various artists diff --git a/approot/messages_fr.xml b/approot/messages_fr.xml index 310fe585..955938ae 100644 --- a/approot/messages_fr.xml +++ b/approot/messages_fr.xml @@ -128,8 +128,11 @@ Ajoutés récemment Joués récemment Albums +Ajouter aux favoris +Favoris Pistes Type +Retirer des favoris Valeur Artistes divers diff --git a/src/libs/database/impl/Artist.cpp b/src/libs/database/impl/Artist.cpp index 267c0eee..2f41aa80 100644 --- a/src/libs/database/impl/Artist.cpp +++ b/src/libs/database/impl/Artist.cpp @@ -303,7 +303,11 @@ Artist::getByFilter(Session& session, } std::vector -Artist::getLastWritten(Session& session, std::optional after, const std::set& clusters, std::optional linkType, std::optional range, bool& moreResults) +Artist::getLastWritten(Session& session, + std::optional after, + const std::set& clusters, + std::optional linkType, + std::optional range, bool& moreResults) { session.checkSharedLocked(); @@ -330,6 +334,58 @@ Artist::getLastWritten(Session& session, std::optional after, con return std::vector(res.begin(), res.end()); } +std::vector +Artist::getStarred(Session& session, + User::pointer user, + const std::set& clusters, + std::optional linkType, + SortMethod sortMethod, + std::optional range, bool& moreResults) +{ + session.checkSharedLocked(); + + auto query {createQuery(session, "SELECT DISTINCT a from artist a", clusters, {}, linkType)}; + + { + std::ostringstream oss; + oss << "a.id IN (SELECT DISTINCT a.id FROM artist a" + " INNER JOIN user_artist_starred uas ON uas.artist_id = a.id" + " INNER JOIN user u ON u.id = uas.user_id WHERE u.id = ?)"; + + query.bind(user.id()); + query.where(oss.str()); + } + + switch (sortMethod) + { + case Artist::SortMethod::None: + break; + case Artist::SortMethod::ByName: + query.orderBy("name COLLATE NOCASE"); + break; + case Artist::SortMethod::BySortName: + query.orderBy("sort_name COLLATE NOCASE"); + break; + } + + Wt::Dbo::collection collection = query + .groupBy("a.id") + .limit(range ? static_cast(range->limit) + 1 : -1) + .offset(range ? static_cast(range->offset) : -1); + + auto res {std::vector(collection.begin(), collection.end())}; + + if (range && res.size() == static_cast(range->limit) + 1) + { + moreResults = true; + res.pop_back(); + } + else + moreResults = false; + + return std::vector(res.begin(), res.end()); +} + std::vector> Artist::getReleases(const std::set& clusterIds) const { diff --git a/src/libs/database/impl/Release.cpp b/src/libs/database/impl/Release.cpp index 5b2e088c..64a04b31 100644 --- a/src/libs/database/impl/Release.cpp +++ b/src/libs/database/impl/Release.cpp @@ -245,6 +245,46 @@ Release::getByYear(Session& session, int yearFrom, int yearTo, std::optional(res.begin(), res.end()); } +std::vector +Release::getStarred(Session& session, + User::pointer user, + const std::set& clusterIds, + std::optional range, + bool& moreResults) +{ + session.checkSharedLocked(); + + auto query {createQuery(session, "SELECT r from release r", clusterIds, {})}; + { + std::ostringstream oss; + oss << "r.id IN (SELECT DISTINCT r.id FROM release r" + " INNER JOIN user_release_starred urs ON urs.release_id = r.id" + " INNER JOIN user u ON u.id = urs.user_id WHERE u.id = ?)"; + + query.bind(user.id()); + query.where(oss.str()); + } + + Wt::Dbo::collection collection = query + .groupBy("r.id") + .orderBy("r.name COLLATE NOCASE") + .offset(range ? static_cast(range->offset) : -1) + .limit(range ? static_cast(range->limit) + 1: -1); + + auto res {std::vector(collection.begin(), collection.end())}; + if (range && res.size() == static_cast(range->limit) + 1) + { + moreResults = true; + res.pop_back(); + } + else + moreResults = false; + + return res; + + +} + std::vector Release::getByClusters(Session& session, const std::set& clusters) { diff --git a/src/libs/database/impl/Track.cpp b/src/libs/database/impl/Track.cpp index 04a16f1f..aaf4b53a 100644 --- a/src/libs/database/impl/Track.cpp +++ b/src/libs/database/impl/Track.cpp @@ -265,6 +265,41 @@ Track::getAllIdsWithClusters(Session& session, std::optional limit) return std::vector(res.begin(), res.end()); } +std::vector +Track::getStarred(Session& session, + Wt::Dbo::ptr user, + const std::set& clusterIds, + std::optional range, bool& moreResults) +{ + session.checkSharedLocked(); + + auto query {createQuery(session, "SELECT t from track t", clusterIds, {})}; + { + std::ostringstream oss; + oss << "t.id IN (SELECT DISTINCT t.id FROM track t" + " INNER JOIN user_track_starred uts ON uts.track_id = t.id" + " INNER JOIN user u ON u.id = uts.user_id WHERE u.id = ?)"; + + query.bind(user.id()); + query.where(oss.str()); + } + + Wt::Dbo::collection collection = query + .offset(range ? static_cast(range->offset) : -1) + .limit(range ? static_cast(range->limit) + 1: -1); + + auto res {std::vector(collection.begin(), collection.end())}; + if (range && res.size() == static_cast(range->limit) + 1) + { + moreResults = true; + res.pop_back(); + } + else + moreResults = false; + + return res; +} + std::vector Track::getClusters() const { diff --git a/src/libs/database/impl/User.cpp b/src/libs/database/impl/User.cpp index 26321542..df761cff 100644 --- a/src/libs/database/impl/User.cpp +++ b/src/libs/database/impl/User.cpp @@ -172,12 +172,6 @@ User::hasStarredArtist(Wt::Dbo::ptr artist) const return _starredArtists.count(artist) != 0; } -std::vector> -User::getStarredArtists() const -{ - return std::vector>(_starredArtists.begin(), _starredArtists.end()); -} - void User::starRelease(Wt::Dbo::ptr release) { @@ -198,17 +192,6 @@ User::hasStarredRelease(Wt::Dbo::ptr release) const return _starredReleases.count(release) != 0; } -std::vector> -User::getStarredReleases(std::optional offset, std::optional limit) const -{ - Wt::Dbo::collection> res = _starredReleases.find() - .offset(offset ? static_cast(*offset) : -1) - .limit(limit ? static_cast(*limit) : -1); - - return std::vector>(res.begin(), res.end()); -} - - void User::starTrack(Wt::Dbo::ptr track) { @@ -229,12 +212,6 @@ User::hasStarredTrack(Wt::Dbo::ptr track) const return _starredTracks.count(track) != 0; } -std::vector> -User::getStarredTracks() const -{ - return std::vector>(_starredTracks.begin(), _starredTracks.end()); -} - } // namespace Database diff --git a/src/libs/database/include/database/Artist.hpp b/src/libs/database/include/database/Artist.hpp index 642e4aa8..472ce5b4 100644 --- a/src/libs/database/include/database/Artist.hpp +++ b/src/libs/database/include/database/Artist.hpp @@ -86,6 +86,12 @@ class Artist : public Wt::Dbo::Dbo std::optional, bool& moreResults); static std::vector getAllIdsWithClusters(Session& session, std::optional limit = {}); + static std::vector getStarred(Session& session, + Wt::Dbo::ptr user, + const std::set& clusters, + std::optional linkType, // if set, only artists that have produced at least one track with this link type + SortMethod sortMethod, + std::optional, bool& moreResults); // Accessors const std::string& getName() const { return _name; } diff --git a/src/libs/database/include/database/Release.hpp b/src/libs/database/include/database/Release.hpp index 8b411b19..bfa10b18 100644 --- a/src/libs/database/include/database/Release.hpp +++ b/src/libs/database/include/database/Release.hpp @@ -59,6 +59,7 @@ class Release : public Wt::Dbo::Dbo static std::vector getAllIdsRandom(Session& session, const std::set& clusters, std::optional size = {}); static std::vector getLastWritten(Session& session, std::optional after, const std::set& clusters, std::optional range, bool& moreResults); static std::vector getByYear(Session& session, int yearFrom, int yearTo, std::optional offset = {}, std::optional size = {}); + static std::vector getStarred(Session& session, Wt::Dbo::ptr user, const std::set& clusters, std::optional range, bool& moreResults); static std::vector getByClusters(Session& session, const std::set& clusters); static std::vector getByFilter(Session& session, diff --git a/src/libs/database/include/database/Track.hpp b/src/libs/database/include/database/Track.hpp index 52a5113a..4a8849ec 100644 --- a/src/libs/database/include/database/Track.hpp +++ b/src/libs/database/include/database/Track.hpp @@ -81,6 +81,10 @@ class Track : public Wt::Dbo::Dbo static std::vector getAllWithMBIDAndMissingFeatures(Session& session); static std::vector getAllIdsWithFeatures(Session& session, std::optional limit = {}); static std::vector getAllIdsWithClusters(Session& session, std::optional limit = {}); + static std::vector getStarred(Session& session, + Wt::Dbo::ptr user, + const std::set& clusters, + std::optional range, bool& hasMore); // Create utility static pointer create(Session& session, const std::filesystem::path& p); diff --git a/src/libs/database/include/database/User.hpp b/src/libs/database/include/database/User.hpp index 8d3e8851..73f0749b 100644 --- a/src/libs/database/include/database/User.hpp +++ b/src/libs/database/include/database/User.hpp @@ -197,18 +197,15 @@ class User : public Wt::Dbo::Dbo void starArtist(Wt::Dbo::ptr artist); void unstarArtist(Wt::Dbo::ptr artist); bool hasStarredArtist(Wt::Dbo::ptr artist) const; - std::vector> getStarredArtists() const; void starRelease(Wt::Dbo::ptr release); void unstarRelease(Wt::Dbo::ptr release); bool hasStarredRelease(Wt::Dbo::ptr release) const; - std::vector> getStarredReleases(std::optional offset = {}, std::optional size = {}) const; // Stars void starTrack(Wt::Dbo::ptr track); void unstarTrack(Wt::Dbo::ptr track); bool hasStarredTrack(Wt::Dbo::ptr track) const; - std::vector> getStarredTracks() const; template void persist(Action& a) diff --git a/src/libs/subsonic/impl/SubsonicResource.cpp b/src/libs/subsonic/impl/SubsonicResource.cpp index f5cbd81b..4b5dddef 100644 --- a/src/libs/subsonic/impl/SubsonicResource.cpp +++ b/src/libs/subsonic/impl/SubsonicResource.cpp @@ -743,7 +743,8 @@ handleGetAlbumListRequestCommon(const RequestContext& context, bool id3) } else if (type == "starred") { - releases = user->getStarredReleases(offset, size); + bool moreResults {}; + releases = Release::getStarred(context.dbSession, user, {}, Range {offset, size}, moreResults); } else if (type == "byGenre") { @@ -1172,19 +1173,22 @@ handleGetStarredRequestCommon(RequestContext& context, bool id3) Response::Node& starredNode {response.createNode(id3 ? "starred2" : "starred")}; { - auto artists {user->getStarredArtists()}; + bool moreResults {}; + const auto artists {Artist::getStarred(context.dbSession, user, {}, std::nullopt, Artist::SortMethod::BySortName, std::nullopt, moreResults)}; for (const Artist::pointer& artist : artists) starredNode.addArrayChild("artist", artistToResponseNode(user, artist, id3)); } { - auto releases {user->getStarredReleases()}; + bool moreResults {}; + const auto releases {Release::getStarred(context.dbSession, user, {}, std::nullopt, moreResults)}; for (const Release::pointer& release : releases) starredNode.addArrayChild("album", releaseToResponseNode(release, context.dbSession, user, id3)); } { - auto tracks {user->getStarredTracks()}; + bool moreResults {}; + const auto tracks {Track::getStarred(context.dbSession, user, {}, std::nullopt, moreResults)}; for (const Track::pointer& track : tracks) starredNode.addArrayChild("song", trackToResponseNode(track, context.dbSession, user)); } diff --git a/src/lms/CMakeLists.txt b/src/lms/CMakeLists.txt index 6bb7e998..3239f633 100644 --- a/src/lms/CMakeLists.txt +++ b/src/lms/CMakeLists.txt @@ -28,6 +28,7 @@ add_executable(lms ui/explore/ReleaseView.cpp ui/explore/SearchView.cpp ui/explore/TrackListHelpers.cpp + ui/explore/TrackPopup.cpp ui/explore/TracksView.cpp ui/resource/AudioFileResource.cpp ui/resource/AudioTranscodeResource.cpp diff --git a/src/lms/ui/explore/ArtistView.cpp b/src/lms/ui/explore/ArtistView.cpp index 58bf1f4d..297250db 100644 --- a/src/lms/ui/explore/ArtistView.cpp +++ b/src/lms/ui/explore/ArtistView.cpp @@ -27,6 +27,7 @@ #include "database/Artist.hpp" #include "database/Release.hpp" #include "database/ScanSettings.hpp" +#include "database/User.hpp" #include "recommendation/IEngine.hpp" #include "utils/Logger.hpp" #include "utils/String.hpp" @@ -131,6 +132,28 @@ Artist::refreshView() { artistsAction.emit(PlayQueueAction::PlayLast, {*artistId}); }); + + bool isStarred {}; + { + auto transaction {LmsApp->getDbSession().createSharedTransaction()}; + + if (auto artist {Database::Artist::getById(LmsApp->getDbSession(), *artistId)}) + isStarred = LmsApp->getUser()->hasStarredArtist(artist); + } + popup->addItem(Wt::WString::tr(isStarred ? "Lms.Explore.unstar" : "Lms.Explore.star")) + ->triggered().connect(this, [=] + { + auto transaction {LmsApp->getDbSession().createUniqueTransaction()}; + + auto artist {Database::Artist::getById(LmsApp->getDbSession(), *artistId)}; + if (!artist) + return; + + if (isStarred) + LmsApp->getUser().modify()->unstarArtist(artist); + else + LmsApp->getUser().modify()->starArtist(artist); + }); popup->addItem(Wt::WString::tr("Lms.Explore.download")) ->setLink(Wt::WLink {std::make_unique(*artistId)}); diff --git a/src/lms/ui/explore/ArtistsView.cpp b/src/lms/ui/explore/ArtistsView.cpp index 0084d009..a37025dc 100644 --- a/src/lms/ui/explore/ArtistsView.cpp +++ b/src/lms/ui/explore/ArtistsView.cpp @@ -59,6 +59,7 @@ Artists::Artists(Filters* filters) }; addItem(*menu, Wt::WString::tr("Lms.Explore.random"), Mode::Random); + addItem(*menu, Wt::WString::tr("Lms.Explore.starred"), Mode::Starred); addItem(*menu, Wt::WString::tr("Lms.Explore.recently-played"), Mode::RecentlyPlayed); addItem(*menu, Wt::WString::tr("Lms.Explore.most-played"), Mode::MostPlayed); addItem(*menu, Wt::WString::tr("Lms.Explore.recently-added"), Mode::RecentlyAdded); @@ -167,6 +168,15 @@ Artists::getArtists(std::optional range, bool& moreResults) artists = getRandomArtists(range, moreResults); break; + case Mode::Starred: + artists = Artist::getStarred(LmsApp->getDbSession(), + LmsApp->getUser(), + _filters->getClusterIds(), + linkType, + Artist::SortMethod::BySortName, + range, moreResults); + break; + case Mode::RecentlyPlayed: artists = LmsApp->getUser()->getPlayedTrackList(LmsApp->getDbSession()) ->getArtistsReverse(_filters->getClusterIds(), diff --git a/src/lms/ui/explore/ArtistsView.hpp b/src/lms/ui/explore/ArtistsView.hpp index 1daf80ed..1a819e77 100644 --- a/src/lms/ui/explore/ArtistsView.hpp +++ b/src/lms/ui/explore/ArtistsView.hpp @@ -46,6 +46,7 @@ class Artists : public Wt::WTemplate enum class Mode { Random, + Starred, RecentlyPlayed, RecentlyAdded, MostPlayed, diff --git a/src/lms/ui/explore/ReleasePopup.cpp b/src/lms/ui/explore/ReleasePopup.cpp index 19755b33..0b57b6f0 100644 --- a/src/lms/ui/explore/ReleasePopup.cpp +++ b/src/lms/ui/explore/ReleasePopup.cpp @@ -19,6 +19,8 @@ #include "ReleasePopup.hpp" +#include "database/Release.hpp" +#include "database/User.hpp" #include "resource/DownloadResource.hpp" #include "LmsApplication.hpp" @@ -42,6 +44,29 @@ namespace UserInterface { releasesAction.emit(PlayQueueAction::PlayLast, {releaseId}); }); + + bool isStarred {}; + { + auto transaction {LmsApp->getDbSession().createSharedTransaction()}; + + if (auto release {Database::Release::getById(LmsApp->getDbSession(), releaseId)}) + isStarred = LmsApp->getUser()->hasStarredRelease(release); + } + + popup->addItem(Wt::WString::tr(isStarred ? "Lms.Explore.unstar" : "Lms.Explore.star")) + ->triggered().connect(&target, [=] + { + auto transaction {LmsApp->getDbSession().createUniqueTransaction()}; + + auto release {Database::Release::getById(LmsApp->getDbSession(), releaseId)}; + if (!release) + return; + + if (isStarred) + LmsApp->getUser().modify()->unstarRelease(release); + else + LmsApp->getUser().modify()->starRelease(release); + }); popup->addItem(Wt::WString::tr("Lms.Explore.download")) ->setLink(Wt::WLink {std::make_unique(releaseId)}); diff --git a/src/lms/ui/explore/ReleaseView.cpp b/src/lms/ui/explore/ReleaseView.cpp index aa2f9f5e..540072c2 100644 --- a/src/lms/ui/explore/ReleaseView.cpp +++ b/src/lms/ui/explore/ReleaseView.cpp @@ -40,6 +40,7 @@ #include "MediaPlayer.hpp" #include "ReleaseListHelpers.hpp" #include "ReleasePopup.hpp" +#include "TrackPopup.hpp" #include "TrackStringUtils.hpp" using namespace Database; @@ -241,17 +242,7 @@ Release::refreshView() Wt::WText* moreBtn {entry->bindNew("more-btn", Wt::WString::tr("Lms.Explore.template.more-btn"), Wt::TextFormat::XHTML)}; moreBtn->clicked().connect([=]() { - Wt::WPopupMenu* popup {LmsApp->createPopupMenu()}; - - popup->addItem(Wt::WString::tr("Lms.Explore.play-last")) - ->triggered().connect(moreBtn, [=] - { - tracksAction.emit(PlayQueueAction::PlayLast, {trackId}); - }); - popup->addItem(Wt::WString::tr("Lms.Explore.download")) - ->setLink(Wt::WLink {std::make_unique(trackId)}); - - popup->popup(moreBtn); + displayTrackPopupMenu(*moreBtn, trackId, tracksAction); }); entry->bindString("duration", trackDurationToString(track->getDuration()), Wt::TextFormat::Plain); diff --git a/src/lms/ui/explore/ReleasesView.cpp b/src/lms/ui/explore/ReleasesView.cpp index dd713fef..50ca4c02 100644 --- a/src/lms/ui/explore/ReleasesView.cpp +++ b/src/lms/ui/explore/ReleasesView.cpp @@ -61,6 +61,7 @@ _filters {filters} }; addItem(*menu, Wt::WString::tr("Lms.Explore.random"), Mode::Random); + addItem(*menu, Wt::WString::tr("Lms.Explore.starred"), Mode::Starred); addItem(*menu, Wt::WString::tr("Lms.Explore.recently-played"), Mode::RecentlyPlayed); addItem(*menu, Wt::WString::tr("Lms.Explore.most-played"), Mode::MostPlayed); addItem(*menu, Wt::WString::tr("Lms.Explore.recently-added"), Mode::RecentlyAdded); @@ -198,6 +199,10 @@ Releases::getReleases(std::optional range, bool& moreResults) releases = getRandomReleases(range, moreResults); break; + case Mode::Starred: + releases = Release::getStarred(LmsApp->getDbSession(), LmsApp->getUser(), _filters->getClusterIds(), range, moreResults); + break; + case Mode::RecentlyPlayed: releases = LmsApp->getUser()->getPlayedTrackList(LmsApp->getDbSession())->getReleasesReverse(_filters->getClusterIds(), range, moreResults); break; diff --git a/src/lms/ui/explore/ReleasesView.hpp b/src/lms/ui/explore/ReleasesView.hpp index 479002c3..08aae0d8 100644 --- a/src/lms/ui/explore/ReleasesView.hpp +++ b/src/lms/ui/explore/ReleasesView.hpp @@ -48,6 +48,7 @@ class Releases : public Wt::WTemplate enum class Mode { Random, + Starred, RecentlyPlayed, RecentlyAdded, MostPlayed, diff --git a/src/lms/ui/explore/TrackListHelpers.cpp b/src/lms/ui/explore/TrackListHelpers.cpp index 371fd586..9a8930e7 100644 --- a/src/lms/ui/explore/TrackListHelpers.cpp +++ b/src/lms/ui/explore/TrackListHelpers.cpp @@ -31,6 +31,7 @@ #include "resource/ImageResource.hpp" #include "LmsApplication.hpp" #include "MediaPlayer.hpp" +#include "TrackPopup.hpp" #include "TrackStringUtils.hpp" using namespace Database; @@ -94,20 +95,10 @@ namespace UserInterface::TrackListHelpers tracksAction.emit(PlayQueueAction::Play, {trackId}); }); - Wt::WText* moreBtn = entry->bindNew("more-btn", Wt::WString::tr("Lms.Explore.template.more-btn"), Wt::TextFormat::XHTML); + Wt::WText* moreBtn {entry->bindNew("more-btn", Wt::WString::tr("Lms.Explore.template.more-btn"), Wt::TextFormat::XHTML)}; moreBtn->clicked().connect([=, &tracksAction] { - Wt::WPopupMenu* popup {LmsApp->createPopupMenu()}; - - popup->addItem(Wt::WString::tr("Lms.Explore.play-last")) - ->triggered().connect(moreBtn, [=, &tracksAction] - { - tracksAction.emit(PlayQueueAction::PlayLast, {trackId}); - }); - popup->addItem(Wt::WString::tr("Lms.Explore.download")) - ->setLink(Wt::WLink {std::make_unique(trackId)}); - - popup->popup(moreBtn); + displayTrackPopupMenu(*moreBtn, trackId, tracksAction); }); LmsApp->getMediaPlayer().trackLoaded.connect(entryPtr, [=] (Database::IdType loadedTrackId) diff --git a/src/lms/ui/explore/TrackPopup.cpp b/src/lms/ui/explore/TrackPopup.cpp new file mode 100644 index 00000000..bb1b92c6 --- /dev/null +++ b/src/lms/ui/explore/TrackPopup.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2020 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include "TrackPopup.hpp" + +#include "database/Track.hpp" +#include "database/User.hpp" +#include "resource/DownloadResource.hpp" +#include "LmsApplication.hpp" + +namespace UserInterface +{ + + void + displayTrackPopupMenu(Wt::WInteractWidget& target, + Database::IdType trackId, + PlayQueueActionSignal& tracksAction) + { + Wt::WPopupMenu* popup {LmsApp->createPopupMenu()}; + + popup->addItem(Wt::WString::tr("Lms.Explore.play-last")) + ->triggered().connect(&target, [=, &tracksAction] + { + tracksAction.emit(PlayQueueAction::PlayLast, {trackId}); + }); + + bool isStarred {}; + { + auto transaction {LmsApp->getDbSession().createSharedTransaction()}; + + if (auto track {Database::Track::getById(LmsApp->getDbSession(), trackId)}) + isStarred = LmsApp->getUser()->hasStarredTrack(track); + } + popup->addItem(Wt::WString::tr(isStarred ? "Lms.Explore.unstar" : "Lms.Explore.star")) + ->triggered().connect(&target, [=] + { + auto transaction {LmsApp->getDbSession().createUniqueTransaction()}; + + auto track {Database::Track::getById(LmsApp->getDbSession(), trackId)}; + if (!track) + return; + + if (isStarred) + LmsApp->getUser().modify()->unstarTrack(track); + else + LmsApp->getUser().modify()->starTrack(track); + }); + popup->addItem(Wt::WString::tr("Lms.Explore.download")) + ->setLink(Wt::WLink {std::make_unique(trackId)}); + + popup->popup(&target); + } + +} // namespace UserInterface + diff --git a/src/lms/ui/explore/TrackPopup.hpp b/src/lms/ui/explore/TrackPopup.hpp new file mode 100644 index 00000000..66419e6b --- /dev/null +++ b/src/lms/ui/explore/TrackPopup.hpp @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#pragma once + +#include +#include + +#include "database/Types.hpp" +#include "PlayQueueAction.hpp" + +namespace UserInterface +{ + void displayTrackPopupMenu(Wt::WInteractWidget& target, + Database::IdType releaseId, + PlayQueueActionSignal& releasesAction); +} // namespace UserInterface + diff --git a/src/lms/ui/explore/TracksView.cpp b/src/lms/ui/explore/TracksView.cpp index c9451d85..743c6b38 100644 --- a/src/lms/ui/explore/TracksView.cpp +++ b/src/lms/ui/explore/TracksView.cpp @@ -63,6 +63,7 @@ _filters {filters} }; addItem(*menu, Wt::WString::tr("Lms.Explore.random"), Mode::Random); + addItem(*menu, Wt::WString::tr("Lms.Explore.starred"), Mode::Starred); addItem(*menu, Wt::WString::tr("Lms.Explore.recently-played"), Mode::RecentlyPlayed); addItem(*menu, Wt::WString::tr("Lms.Explore.most-played"), Mode::MostPlayed); addItem(*menu, Wt::WString::tr("Lms.Explore.recently-added"), Mode::RecentlyAdded); @@ -185,6 +186,10 @@ Tracks::getTracks(std::optional range, bool& moreResults) tracks = getRandomTracks(range, moreResults); break; + case Mode::Starred: + tracks = Track::getStarred(LmsApp->getDbSession(), LmsApp->getUser(), _filters->getClusterIds(), range, moreResults); + break; + case Mode::RecentlyPlayed: tracks = LmsApp->getUser()->getPlayedTrackList(LmsApp->getDbSession())->getTracksReverse(_filters->getClusterIds(), range, moreResults); break; diff --git a/src/lms/ui/explore/TracksView.hpp b/src/lms/ui/explore/TracksView.hpp index d66f3b14..3da1d4e6 100644 --- a/src/lms/ui/explore/TracksView.hpp +++ b/src/lms/ui/explore/TracksView.hpp @@ -47,6 +47,7 @@ class Tracks : public Wt::WTemplate enum class Mode { Random, + Starred, RecentlyPlayed, RecentlyAdded, MostPlayed, diff --git a/src/test/database/DatabaseTest.cpp b/src/test/database/DatabaseTest.cpp index 8da1ecf6..a8ddcb5d 100644 --- a/src/test/database/DatabaseTest.cpp +++ b/src/test/database/DatabaseTest.cpp @@ -1248,22 +1248,26 @@ void testSingleStarredArtist(Session& session) { ScopedArtist artist {session, "MyArtist"}; + ScopedTrack track {session, "MyTrack"}; ScopedUser user {session, "MyUser"}; { auto transaction {session.createSharedTransaction()}; + auto trackArtistLink {TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLink::Type::Artist)}; user.get().modify()->starArtist(artist.get()); } { auto transaction {session.createSharedTransaction()}; - auto artists {user->getStarredArtists()}; + CHECK(user->hasStarredArtist(artist.get())); + + bool hasMore {}; + auto artists {Artist::getStarred(session, user.get(), {}, std::nullopt, Artist::SortMethod::BySortName, std::nullopt, hasMore)}; CHECK(artists.size() == 1); CHECK(artists.front().id() == artist.getId()); - - CHECK(user->hasStarredArtist(artist.get())); + CHECK(hasMore == false); } } @@ -1272,22 +1276,26 @@ void testSingleStarredRelease(Session& session) { ScopedRelease release {session, "MyRelease"}; + ScopedTrack track {session, "MyTrack"}; ScopedUser user {session, "MyUser"}; { auto transaction {session.createUniqueTransaction()}; + track.get().modify()->setRelease(release.get()); user.get().modify()->starRelease(release.get()); } { auto transaction {session.createSharedTransaction()}; - auto releases {user->getStarredReleases()}; + CHECK(user->hasStarredRelease(release.get())); + + bool hasMore {}; + auto releases {Release::getStarred(session, user.get(), {}, std::nullopt, hasMore)}; CHECK(releases.size() == 1); CHECK(releases.front().id() == release.getId()); - - CHECK(user->hasStarredRelease(release.get())); + CHECK(hasMore == false); } } @@ -1307,11 +1315,13 @@ testSingleStarredTrack(Session& session) { auto transaction {session.createSharedTransaction()}; - auto tracks {user->getStarredTracks()}; + CHECK(user->hasStarredTrack(track.get())); + + bool hasMore {}; + auto tracks {Track::getStarred(session, user.get(), {}, std::nullopt, hasMore)}; CHECK(tracks.size() == 1); CHECK(tracks.front().id() == track.getId()); - - CHECK(user->hasStarredTrack(track.get())); + CHECK(hasMore == false); } }