diff --git a/Makefile.am b/Makefile.am index 0fba14a7..28e6ebfb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -41,7 +41,8 @@ lms_approot_DATA = \ approot/releasesinfo.xml \ approot/settings.xml \ approot/templates.xml \ - approot/tracks.xml + approot/tracks.xml \ + approot/tracksinfo.xml sysconf_DATA = \ $(top_srcdir)/conf/lms.conf diff --git a/approot/messages.xml b/approot/messages.xml index 02350719..608a5fe3 100644 --- a/approot/messages.xml +++ b/approot/messages.xml @@ -81,6 +81,11 @@ Recently added Top releases + +Recently added +Top tracks + + {1} tracks Added {1} tracks diff --git a/approot/tracksinfo.xml b/approot/tracksinfo.xml new file mode 100644 index 00000000..2eaa98c2 --- /dev/null +++ b/approot/tracksinfo.xml @@ -0,0 +1,20 @@ + + + + + +

${tr:Lms.Explore.TracksInfo.most-played}

+ ${most-played} +

${tr:Lms.Explore.TracksInfo.recently-added}

+ ${recently-added} +
+ + +
+
+ ${name} +
+
+
+ +
diff --git a/docroot/css/lms.css b/docroot/css/lms.css index 5ac424dd..4d90cf9b 100644 --- a/docroot/css/lms.css +++ b/docroot/css/lms.css @@ -169,6 +169,16 @@ a:hover { text-align: center; } +.Lms-explore-tracksinfo-entry { + min-height: 28px; + margin: 0px; +} + +.Lms-explore-tracksinfo-entry:hover { + background-color: lightgrey; +} + + .Lms-explore-btn { font-size: 22px !important; cursor: pointer; diff --git a/src/Makefile.am b/src/Makefile.am index bf097b57..0e1bfe72 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -31,13 +31,14 @@ lms_SOURCES = \ $(srcdir)/ui/admin/UsersView.cpp \ $(srcdir)/ui/common/Validators.cpp \ $(srcdir)/ui/explore/ArtistView.cpp \ - $(srcdir)/ui/explore/ArtistsView.cpp \ $(srcdir)/ui/explore/ArtistsInfoView.cpp \ + $(srcdir)/ui/explore/ArtistsView.cpp \ $(srcdir)/ui/explore/Explore.cpp \ $(srcdir)/ui/explore/Filters.cpp \ - $(srcdir)/ui/explore/ReleasesView.cpp \ $(srcdir)/ui/explore/ReleasesInfoView.cpp \ + $(srcdir)/ui/explore/ReleasesView.cpp \ $(srcdir)/ui/explore/ReleaseView.cpp \ + $(srcdir)/ui/explore/TracksInfoView.cpp \ $(srcdir)/ui/explore/TracksView.cpp \ $(srcdir)/ui/resource/ImageResource.cpp \ $(srcdir)/ui/resource/TranscodeResource.cpp \ diff --git a/src/database/Track.cpp b/src/database/Track.cpp index 6fdf6c49..d7535afe 100644 --- a/src/database/Track.cpp +++ b/src/database/Track.cpp @@ -96,6 +96,17 @@ Track::getChecksumDuplicates(Wt::Dbo::Session& session) return std::vector(res.begin(), res.end()); } +std::vector +Track::getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, int limit) +{ + Wt::Dbo::collection res = session.find() + .where("file_added > ?").bind(after) + .orderBy("file_added DESC") + .limit(limit); + + return std::vector(res.begin(), res.end()); +} + std::vector Track::getClusters(void) const { diff --git a/src/database/Track.hpp b/src/database/Track.hpp index 550df65e..334ce2d9 100644 --- a/src/database/Track.hpp +++ b/src/database/Track.hpp @@ -27,7 +27,6 @@ #include #include - #include #include "Types.hpp" @@ -74,6 +73,7 @@ class Track : public Wt::Dbo::Dbo static std::vector getAllPaths(Wt::Dbo::Session& session); // nested transaction static std::vector getMBIDDuplicates(Wt::Dbo::Session& session); static std::vector getChecksumDuplicates(Wt::Dbo::Session& session); + static std::vector getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, int size = 1); // Create utility static pointer create(Wt::Dbo::Session& session, const boost::filesystem::path& p); diff --git a/src/database/TrackList.cpp b/src/database/TrackList.cpp index d7983c57..55f7c86d 100644 --- a/src/database/TrackList.cpp +++ b/src/database/TrackList.cpp @@ -222,6 +222,21 @@ TrackList::getTopReleases(int limit) const return std::vector(res.begin(), res.end()); } +std::vector +TrackList::getTopTracks(int limit) const +{ + assert(session()); + assert(IdIsValid(self()->id())); + + Wt::Dbo::collection res = session()->query("SELECT t from track t INNER JOIN tracklist_entry p_e ON p_e.track_id = t.id INNER JOIN tracklist p ON p.id = p_e.tracklist_id") + .where("p.id = ?").bind(self()->id()) + .groupBy("t.id") + .orderBy("COUNT(t.id) DESC") + .limit(limit); + + return std::vector(res.begin(), res.end()); +} + TrackListEntry::TrackListEntry(Wt::Dbo::ptr track, Wt::Dbo::ptr tracklist) : _track(track), _tracklist(tracklist) diff --git a/src/database/TrackList.hpp b/src/database/TrackList.hpp index 60637b9d..c84502b3 100644 --- a/src/database/TrackList.hpp +++ b/src/database/TrackList.hpp @@ -45,6 +45,7 @@ class TrackList : public Wt::Dbo::Dbo // Stats utility std::vector> getTopArtists(int limit = 1) const; std::vector> getTopReleases(int limit = 1) const; + std::vector> getTopTracks(int limit = 1) const; // Search utility static pointer get(Wt::Dbo::Session& session, std::string name, Wt::Dbo::ptr user); diff --git a/src/ui/LmsApplication.cpp b/src/ui/LmsApplication.cpp index 0893b9a5..2435f6f2 100644 --- a/src/ui/LmsApplication.cpp +++ b/src/ui/LmsApplication.cpp @@ -106,8 +106,9 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnecti messageResourceBundle().use(appRoot() + "releases"); messageResourceBundle().use(appRoot() + "releasesinfo"); messageResourceBundle().use(appRoot() + "settings"); - messageResourceBundle().use(appRoot() + "tracks"); messageResourceBundle().use(appRoot() + "templates"); + messageResourceBundle().use(appRoot() + "tracks"); + messageResourceBundle().use(appRoot() + "tracksinfo"); // Require js here to avoid async problems requireJQuery("/js/jquery-1.10.2.min.js"); diff --git a/src/ui/explore/Explore.cpp b/src/ui/explore/Explore.cpp index 1b9f8ce9..b77320d2 100644 --- a/src/ui/explore/Explore.cpp +++ b/src/ui/explore/Explore.cpp @@ -27,13 +27,14 @@ #include "LmsApplication.hpp" -#include "Filters.hpp" -#include "ArtistsView.hpp" #include "ArtistsInfoView.hpp" +#include "ArtistsView.hpp" #include "ArtistView.hpp" -#include "ReleasesView.hpp" +#include "Filters.hpp" #include "ReleasesInfoView.hpp" +#include "ReleasesView.hpp" #include "ReleaseView.hpp" +#include "TracksInfoView.hpp" #include "TracksView.hpp" namespace UserInterface { @@ -80,7 +81,7 @@ handleInfoPathChange(Wt::WStackedWidget* stack) { IdxArtists = 0, IdxReleases, - IdxEmpty, + IdxTracks, }; static const std::map indexes = @@ -89,7 +90,7 @@ handleInfoPathChange(Wt::WStackedWidget* stack) { "/artist", IdxArtists }, { "/releases", IdxReleases }, { "/release", IdxReleases }, - { "/tracks", IdxEmpty }, + { "/tracks", IdxTracks }, }; LMS_LOG(UI, DEBUG) << "Internal path changed to '" << wApp->internalPath() << "'"; @@ -158,18 +159,22 @@ Explore::Explore() auto releasesInfoRaw = releasesInfo.get(); infoStack->addWidget(std::move(releasesInfo)); - infoStack->addWidget(std::make_unique()); + auto tracksInfo = std::make_unique(); + auto tracksInfoRaw = tracksInfo.get(); + infoStack->addWidget(std::move(tracksInfo)); _dbChanged.connect([=] { artistsInfoRaw->refreshRecentlyAdded(); releasesInfoRaw->refreshRecentlyAdded(); + tracksInfoRaw->refreshRecentlyAdded(); }); _trackPlayed.connect([=] { artistsInfoRaw->refreshMostPlayed(); releasesInfoRaw->refreshMostPlayed(); + tracksInfoRaw->refreshMostPlayed(); }); diff --git a/src/ui/explore/TracksInfoView.cpp b/src/ui/explore/TracksInfoView.cpp new file mode 100644 index 00000000..ed2c9b18 --- /dev/null +++ b/src/ui/explore/TracksInfoView.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2018 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 "TracksInfoView.hpp" + +#include +#include + +#include "database/Track.hpp" +#include "database/TrackList.hpp" + +#include "utils/Utils.hpp" + +#include "LmsApplication.hpp" + +using namespace Database; + +namespace { + +using namespace UserInterface; + +void addEntries(Wt::WContainerWidget *container, const std::vector& tracks) +{ + for (auto track : tracks) + { + Wt::WTemplate* entry = container->addNew(Wt::WString::tr("Lms.Explore.TracksInfo.template.entry")); + + entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain); + } +} + +} + +namespace UserInterface { + +TracksInfo::TracksInfo() +: Wt::WTemplate(Wt::WString::tr("Lms.Explore.TracksInfo.template")) +{ + addFunction("tr", &Wt::WTemplate::Functions::tr); + + _mostPlayedContainer = bindNew("most-played"); + _recentlyAddedContainer = bindNew("recently-added"); + + refreshMostPlayed(); + refreshRecentlyAdded(); +} + +void +TracksInfo::refreshRecentlyAdded() +{ + auto after = Wt::WLocalDateTime::currentServerDateTime().toUTC().addMonths(-1); + + Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); + auto tracks = Track::getLastAdded(LmsApp->getDboSession(), after, 5); + + _recentlyAddedContainer->clear(); + addEntries(_recentlyAddedContainer, tracks); +} + +void +TracksInfo::refreshMostPlayed() +{ + Wt::Dbo::Transaction transaction(LmsApp->getDboSession()); + auto tracks = LmsApp->getUser()->getPlayedTrackList()->getTopTracks(5); + + _mostPlayedContainer->clear(); + addEntries(_mostPlayedContainer, tracks); +} + +} // namespace UserInterface + diff --git a/src/ui/explore/TracksInfoView.hpp b/src/ui/explore/TracksInfoView.hpp new file mode 100644 index 00000000..76d9ca78 --- /dev/null +++ b/src/ui/explore/TracksInfoView.hpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018 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 + +namespace UserInterface { + +class TracksInfo : public Wt::WTemplate +{ + public: + TracksInfo(); + + void refreshRecentlyAdded(); + void refreshMostPlayed(); + + private: + Wt::WContainerWidget* _mostPlayedContainer; + Wt::WContainerWidget* _recentlyAddedContainer; +}; + +} // namespace UserInterface +