diff --git a/approot/artists.xml b/approot/artists.xml
index c5c70050..23425af9 100644
--- a/approot/artists.xml
+++ b/approot/artists.xml
@@ -7,6 +7,12 @@
${tr:Lms.Explore.most-played-artists}
${most-played-artists}
+
+
+ ${recently-added-artists}
+
@@ -20,7 +26,7 @@
${show-more}
-
+
${name}
diff --git a/approot/messages.xml b/approot/messages.xml
index 5312e568..e50b99e3 100644
--- a/approot/messages.xml
+++ b/approot/messages.xml
@@ -58,6 +58,8 @@
Artists
Most played artists
Most played releases
+Recently added artists
+Recently added releases
Albums
Search...
Show more
diff --git a/approot/releases.xml b/approot/releases.xml
index a77d74c6..92cf00ce 100644
--- a/approot/releases.xml
+++ b/approot/releases.xml
@@ -7,6 +7,12 @@
${tr:Lms.Explore.most-played-releases}
${most-played-releases}
+
+
+ ${recently-added-releases}
+
@@ -28,7 +34,7 @@
-
+
${cover}
${release-name}
${}${artist-name}${}
diff --git a/src/database/Artist.cpp b/src/database/Artist.cpp
index 2da5adbf..194b8581 100644
--- a/src/database/Artist.cpp
+++ b/src/database/Artist.cpp
@@ -18,6 +18,8 @@
*/
#include "Artist.hpp"
+#include
+
#include "utils/Logger.hpp"
#include "Cluster.hpp"
@@ -140,6 +142,18 @@ Artist::getByFilter(Wt::Dbo::Session& session,
return res;
}
+std::vector
+Artist::getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, int limit)
+{
+ Wt::Dbo::collection res = session.query("SELECT a from artist a INNER JOIN track t ON a.id = t.artist_id")
+ .where("t.file_added > ?").bind(after)
+ .groupBy("a.id")
+ .orderBy("t.file_added DESC")
+ .limit(limit);
+
+ return std::vector(res.begin(), res.end());
+}
+
std::vector>
Artist::getReleases(const std::set& clusterIds) const
{
diff --git a/src/database/Artist.hpp b/src/database/Artist.hpp
index 1c814a0d..2ae1acda 100644
--- a/src/database/Artist.hpp
+++ b/src/database/Artist.hpp
@@ -22,6 +22,7 @@
#include
#include
+#include
#include
#include "Types.hpp"
@@ -56,6 +57,7 @@ class Artist : public Wt::Dbo::Dbo
static std::vector getAll(Wt::Dbo::Session& session, int offset = -1, int size = -1);
static std::vector getAllOrphans(Wt::Dbo::Session& session); // No track related
+ static std::vector getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, int size = 1);
// Accessors
std::string getName(void) const { return _name; }
diff --git a/src/database/Release.cpp b/src/database/Release.cpp
index cc49954f..7cabd1d8 100644
--- a/src/database/Release.cpp
+++ b/src/database/Release.cpp
@@ -74,6 +74,18 @@ Release::getAllOrphans(Wt::Dbo::Session& session)
return std::vector(res.begin(), res.end());
}
+std::vector
+Release::getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, int limit)
+{
+ Wt::Dbo::collection res = session.query("SELECT r from release r INNER JOIN track t ON r.id = t.release_id")
+ .where("t.file_added > ?").bind(after)
+ .groupBy("r.id")
+ .orderBy("t.file_added DESC")
+ .limit(limit);
+
+ return std::vector(res.begin(), res.end());
+}
+
static
Wt::Dbo::Query
getQuery(Wt::Dbo::Session& session,
diff --git a/src/database/Release.hpp b/src/database/Release.hpp
index 32cd1f10..1122314a 100644
--- a/src/database/Release.hpp
+++ b/src/database/Release.hpp
@@ -21,7 +21,7 @@
#include
-#include
+#include
#include "Types.hpp"
@@ -49,6 +49,7 @@ class Release : public Wt::Dbo::Dbo
static pointer getById(Wt::Dbo::Session& session, IdType id);
static std::vector getAllOrphans(Wt::Dbo::Session& session); // no track related
static std::vector getAll(Wt::Dbo::Session& session, int offset, int size);
+ static std::vector getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, int size = 1);
static std::vector getByFilter(Wt::Dbo::Session& session,
const std::set& clusters, // at least one track that belongs to these clusters
diff --git a/src/ui/LmsApplication.cpp b/src/ui/LmsApplication.cpp
index fb25b9cb..90df1480 100644
--- a/src/ui/LmsApplication.cpp
+++ b/src/ui/LmsApplication.cpp
@@ -263,6 +263,9 @@ LmsApplication::handleAuthEvent(void)
LMS_LOG(UI, INFO) << "User '" << LmsApp->getCurrentAuthUser().identity(Wt::Auth::Identity::LoginName) << "' logged in from '" << Wt::WApplication::instance()->environment().clientAddress() << "', user agent = " << Wt::WApplication::instance()->environment().userAgent() << ", session = " << Wt::WApplication::instance()->sessionId();
+ // Since we plug into the Media Scanner events, we need to enable updates
+ enableUpdates(true);
+
{
Wt::Dbo::Transaction transaction (LmsApp->getDboSession());
_isAdmin = LmsApp->getCurrentUser()->isAdmin();
@@ -387,7 +390,6 @@ LmsApplication::handleAuthEvent(void)
// Events from MediaScanner
if (_isAdmin)
{
- enableUpdates(true);
std::string sessionId = this->sessionId();
_scanner.scanComplete().connect(std::bind([=] (Scanner::MediaScanner::Stats stats)
{
diff --git a/src/ui/explore/ArtistsView.cpp b/src/ui/explore/ArtistsView.cpp
index d7e32bf0..26d89f67 100644
--- a/src/ui/explore/ArtistsView.cpp
+++ b/src/ui/explore/ArtistsView.cpp
@@ -20,8 +20,10 @@
#include "ArtistsView.hpp"
#include
-#include
#include
+#include
+#include
+#include
#include "database/Artist.hpp"
#include "database/Setting.hpp"
@@ -35,6 +37,22 @@
using namespace Database;
+namespace {
+
+using namespace UserInterface;
+
+void addCompactEntries(Wt::WContainerWidget *container, const std::vector& artists)
+{
+ for (auto artist : artists)
+ {
+ Wt::WTemplate* entry = container->addNew(Wt::WString::tr("Lms.Explore.Artists.template.compact-entry"));
+
+ entry->bindWidget("name", LmsApplication::createArtistAnchor(artist));
+ }
+}
+
+}
+
namespace UserInterface {
Artists::Artists(Filters* filters)
@@ -47,8 +65,9 @@ Artists::Artists(Filters* filters)
_search->setPlaceholderText(Wt::WString::tr("Lms.Explore.search-placeholder"));
_search->textInput().connect(this, &Artists::refresh);
- _artistsContainer = bindNew("artists");
- _mostPlayedArtistsContainer = bindNew("most-played-artists");
+ _container = bindNew("artists");
+ _mostPlayedContainer = bindNew("most-played-artists");
+ _recentlyAddedContainer = bindNew("recently-added-artists");
_showMore = bindNew("show-more", Wt::WString::tr("Lms.Explore.template.show-more"));
_showMore->addFunction("tr", &Wt::WTemplate::Functions::tr);
@@ -60,30 +79,50 @@ Artists::Artists(Filters* filters)
refresh();
refreshMostPlayed();
+ refreshRecentlyAdded();
filters->updated().connect(this, &Artists::refresh);
+
+ std::string sessionId = LmsApp->sessionId();
+ LmsApp->getMediaScanner().scanComplete().connect([=] (Scanner::MediaScanner::Stats stats)
+ {
+ if (stats.nbChanges() > 0)
+ {
+ Wt::WServer::instance()->post(sessionId, [=]
+ {
+ refreshRecentlyAdded();
+ LmsApp->triggerUpdate();
+ });
+ }
+ });
+}
+
+void
+Artists::refreshRecentlyAdded()
+{
+ auto after = Wt::WLocalDateTime::currentServerDateTime().toUTC().addMonths(-1);
+
+ Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
+ auto artists = Artist::getLastAdded(LmsApp->getDboSession(), after, 5);
+
+ _recentlyAddedContainer->clear();
+ addCompactEntries(_recentlyAddedContainer, artists);
}
void
Artists::refreshMostPlayed()
{
- _mostPlayedArtistsContainer->clear();
-
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto artists = TrackStats::getMostPlayedArtists(LmsApp->getDboSession(), LmsApp->getCurrentUser(), 5);
- for (auto artist : artists)
- {
- Wt::WTemplate* entry = _mostPlayedArtistsContainer->addNew(Wt::WString::tr("Lms.Explore.Artists.template.most-played-entry"));
-
- entry->bindWidget("name", LmsApplication::createArtistAnchor(artist));
- }
+ _mostPlayedContainer->clear();
+ addCompactEntries(_mostPlayedContainer, artists);
}
void
Artists::refresh()
{
- _artistsContainer->clear();
+ _container->clear();
addSome();
}
@@ -100,11 +139,11 @@ Artists::addSome()
auto artists = Artist::getByFilter(LmsApp->getDboSession(),
clusterIds,
searchKeywords,
- _artistsContainer->count(), 20, moreResults);
+ _container->count(), 20, moreResults);
for (auto artist : artists)
{
- Wt::WTemplate* entry = _artistsContainer->addNew(Wt::WString::tr("Lms.Explore.Artists.template.entry"));
+ Wt::WTemplate* entry = _container->addNew(Wt::WString::tr("Lms.Explore.Artists.template.entry"));
entry->bindInt("nb-release", artist->getReleases(clusterIds).size());
entry->bindWidget("name", LmsApplication::createArtistAnchor(artist));
diff --git a/src/ui/explore/ArtistsView.hpp b/src/ui/explore/ArtistsView.hpp
index 78c5b019..3a6f6974 100644
--- a/src/ui/explore/ArtistsView.hpp
+++ b/src/ui/explore/ArtistsView.hpp
@@ -39,6 +39,7 @@ class Artists : public Wt::WTemplate
Wt::Signal artistPlay;
private:
+ void refreshRecentlyAdded();
void refreshMostPlayed();
void refresh();
void addSome();
@@ -46,8 +47,9 @@ class Artists : public Wt::WTemplate
Filters* _filters;
Wt::WTemplate* _showMore;
Wt::WLineEdit* _search;
- Wt::WContainerWidget* _artistsContainer;
- Wt::WContainerWidget* _mostPlayedArtistsContainer;
+ Wt::WContainerWidget* _container;
+ Wt::WContainerWidget* _mostPlayedContainer;
+ Wt::WContainerWidget* _recentlyAddedContainer;
};
} // namespace UserInterface
diff --git a/src/ui/explore/ReleasesView.cpp b/src/ui/explore/ReleasesView.cpp
index 3220cefd..bdbecaf4 100644
--- a/src/ui/explore/ReleasesView.cpp
+++ b/src/ui/explore/ReleasesView.cpp
@@ -19,9 +19,10 @@
#include "ReleasesView.hpp"
-#include
+#include
#include
#include
+#include
#include
#include
@@ -38,46 +39,15 @@
using namespace Database;
-namespace UserInterface {
+namespace {
-Releases::Releases(Filters* filters)
-: Wt::WTemplate(Wt::WString::tr("Lms.Explore.Releases.template")),
-_filters(filters)
+using namespace UserInterface;
+
+void addCompactEntries(Wt::WContainerWidget* container, const std::vector& releases)
{
- addFunction("tr", &Wt::WTemplate::Functions::tr);
-
- _search = bindNew("search");
- _search->setPlaceholderText(Wt::WString::tr("Lms.Explore.search-placeholder"));
- _search->textInput().connect(this, &Releases::refresh);
-
- _releasesContainer = bindNew("releases");
- _mostPlayedReleasesContainer = bindNew("most-played-releases");
-
- _showMore = bindNew("show-more", Wt::WString::tr("Lms.Explore.show-more"));
- _showMore->addFunction("tr", &Wt::WTemplate::Functions::tr);
- _showMore->clicked().connect(std::bind([=]
- {
- addSome();
- }));
-
- refreshMostPlayed();
- refresh();
-
- filters->updated().connect(this, &Releases::refresh);
-}
-
-void
-Releases::refreshMostPlayed()
-{
- _mostPlayedReleasesContainer->clear();
-
- Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
-
- auto releases = TrackStats::getMostPlayedReleases(LmsApp->getDboSession(), LmsApp->getCurrentUser(), 5);
-
for (auto release : releases)
{
- Wt::WTemplate* entry = _mostPlayedReleasesContainer->addNew(Wt::WString::tr("Lms.Explore.Releases.template.most-played-entry"));
+ Wt::WTemplate* entry = container->addNew(Wt::WString::tr("Lms.Explore.Releases.template.compact-entry"));
entry->bindWidget("release-name", LmsApplication::createReleaseAnchor(release));
@@ -100,10 +70,80 @@ Releases::refreshMostPlayed()
}
}
+} // namespace
+
+namespace UserInterface {
+
+Releases::Releases(Filters* filters)
+: Wt::WTemplate(Wt::WString::tr("Lms.Explore.Releases.template")),
+_filters(filters)
+{
+ addFunction("tr", &Wt::WTemplate::Functions::tr);
+
+ _search = bindNew("search");
+ _search->setPlaceholderText(Wt::WString::tr("Lms.Explore.search-placeholder"));
+ _search->textInput().connect(this, &Releases::refresh);
+
+ _container = bindNew("releases");
+ _mostPlayedContainer = bindNew("most-played-releases");
+ _recentlyAddedContainer = bindNew("recently-added-releases");
+
+ _showMore = bindNew("show-more", Wt::WString::tr("Lms.Explore.show-more"));
+ _showMore->addFunction("tr", &Wt::WTemplate::Functions::tr);
+ _showMore->clicked().connect(std::bind([=]
+ {
+ addSome();
+ }));
+
+ refreshRecentlyAdded();
+ refreshMostPlayed();
+ refresh();
+
+ filters->updated().connect(this, &Releases::refresh);
+
+ std::string sessionId = LmsApp->sessionId();
+ LmsApp->getMediaScanner().scanComplete().connect([=] (Scanner::MediaScanner::Stats stats)
+ {
+ if (stats.nbChanges() > 0)
+ {
+ Wt::WServer::instance()->post(sessionId, [=]
+ {
+ refreshRecentlyAdded();
+ LmsApp->triggerUpdate();
+ });
+ }
+ });
+}
+
+void
+Releases::refreshRecentlyAdded()
+{
+ auto after = Wt::WLocalDateTime::currentServerDateTime().toUTC().addMonths(-1);
+
+ Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
+
+ auto releases = Release::getLastAdded(LmsApp->getDboSession(), after, 5);
+
+ _recentlyAddedContainer->clear();
+ addCompactEntries(_recentlyAddedContainer, releases);
+}
+
+void
+Releases::refreshMostPlayed()
+{
+
+ Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
+
+ auto releases = TrackStats::getMostPlayedReleases(LmsApp->getDboSession(), LmsApp->getCurrentUser(), 5);
+
+ _mostPlayedContainer->clear();
+ addCompactEntries(_mostPlayedContainer, releases);
+}
+
void
Releases::refresh()
{
- _releasesContainer->clear();
+ _container->clear();
addSome();
}
@@ -117,13 +157,13 @@ Releases::addSome()
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
bool moreResults;
- auto releases = Release::getByFilter(LmsApp->getDboSession(), clusterIds, searchKeywords, _releasesContainer->count(), 20, moreResults);
+ auto releases = Release::getByFilter(LmsApp->getDboSession(), clusterIds, searchKeywords, _container->count(), 20, moreResults);
for (auto release : releases)
{
auto releaseId = release.id();
- Wt::WTemplate* entry = _releasesContainer->addNew(Wt::WString::tr("Lms.Explore.Releases.template.entry"));
+ Wt::WTemplate* entry = _container->addNew(Wt::WString::tr("Lms.Explore.Releases.template.entry"));
entry->addFunction("tr", Wt::WTemplate::Functions::tr);
Wt::WAnchor* anchor = entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false));
diff --git a/src/ui/explore/ReleasesView.hpp b/src/ui/explore/ReleasesView.hpp
index 934afeeb..5c8374a2 100644
--- a/src/ui/explore/ReleasesView.hpp
+++ b/src/ui/explore/ReleasesView.hpp
@@ -39,6 +39,7 @@ class Releases : public Wt::WTemplate
Wt::Signal releasePlay;
private:
+ void refreshRecentlyAdded();
void refreshMostPlayed();
void refresh();
void addSome();
@@ -46,8 +47,9 @@ class Releases : public Wt::WTemplate
Filters* _filters;
Wt::WTemplate* _showMore;
Wt::WLineEdit* _search;
- Wt::WContainerWidget* _releasesContainer;
- Wt::WContainerWidget* _mostPlayedReleasesContainer;
+ Wt::WContainerWidget* _container;
+ Wt::WContainerWidget* _mostPlayedContainer;
+ Wt::WContainerWidget* _recentlyAddedContainer;
};
} // namespace UserInterface