diff --git a/approot/playlist.xml b/approot/playlist.xml index 077f9557..5b097e83 100644 --- a/approot/playlist.xml +++ b/approot/playlist.xml @@ -22,6 +22,14 @@ ${entries} + + + + + + + + @@ -30,11 +38,12 @@ ${name} - - ${}${release-name}${} - ${}${artist-name}${} - ${play-btn} ${remove-btn} + ${}${release-name}${} + ${}${artist-name}${} + + ${play-btn} ${del-btn} + diff --git a/src/database/Playlist.cpp b/src/database/Playlist.cpp index c63e3777..0094fa7b 100644 --- a/src/database/Playlist.cpp +++ b/src/database/Playlist.cpp @@ -61,6 +61,12 @@ Playlist::getAll(Wt::Dbo::Session& session, Wt::Dbo::ptr user) return std::vector(res.begin(), res.end()); } +void +Playlist::addTrack(Wt::Dbo::ptr track) +{ + _entries.insert( PlaylistEntry::create(*session(), track, self(), _entries.size()) ); +} + PlaylistEntry::PlaylistEntry(Wt::Dbo::ptr track, Wt::Dbo::ptr playlist, int pos) : _pos(pos), _track(track), @@ -74,19 +80,23 @@ PlaylistEntry::create(Wt::Dbo::Session& session, Wt::Dbo::ptr track, Wt:: { return session.add( new PlaylistEntry( track, playlist, pos) ); } -/* -std::vector -PlaylistEntry::getEntries(Wt::Dbo::Session& session, Playlist::pointer playlist) + + +std::vector> +Playlist::getTracks(int offset, int size) const { - typedef Wt::Dbo::collection Entries; + assert(session()); - Entries entries = session.find().where("playlist_id = ?").bind(playlist.id()).orderBy("pos"); + Wt::Dbo::collection> entries = session()->find().where("playlist_id = ?").bind(self().id()).orderBy("pos").offset(offset).limit(size); - std::vector res; - for (Entries::iterator it = entries.begin(); it != entries.end(); ++it) - res.push_back((*it)->getTrack().id()); + std::vector> res; + + for (auto entry : entries) + { + res.push_back(entry->getTrack()); + } return res; -}*/ +} } // namespace Database diff --git a/src/database/Playlist.hpp b/src/database/Playlist.hpp index c2a02a36..b8f62474 100644 --- a/src/database/Playlist.hpp +++ b/src/database/Playlist.hpp @@ -30,7 +30,7 @@ class PlaylistEntry; class User; class Track; -class Playlist +class Playlist : public Wt::Dbo::Dbo { public: typedef Wt::Dbo::ptr pointer; @@ -50,12 +50,11 @@ class Playlist bool isPublic() const { return _isPublic; } // Modifiers - void addTrack(id_type trackId); - void addTracks(const std::vector& trackIds); - void clear(); + void addTrack(Wt::Dbo::ptr track); + void clear() { _entries.clear(); } // Get tracks, ordered by position - std::vector getEntries(Wt::Dbo::Session& session, int offset = -1, int size = -1); + std::vector> getTracks(int offset = -1, int size = -1) const; template void persist(Action& a) @@ -82,7 +81,7 @@ class PlaylistEntry typedef Wt::Dbo::ptr pointer; PlaylistEntry(); - PlaylistEntry(Wt::Dbo::ptr rack, Wt::Dbo::ptr playlist, int position); + PlaylistEntry(Wt::Dbo::ptr track, Wt::Dbo::ptr playlist, int position); // Create utility static pointer create(Wt::Dbo::Session& session, Wt::Dbo::ptr track, Wt::Dbo::ptr playlist, int position); diff --git a/src/database/User.cpp b/src/database/User.cpp index d25410c3..751e614e 100644 --- a/src/database/User.cpp +++ b/src/database/User.cpp @@ -84,7 +84,7 @@ User::getAll(Wt::Dbo::Session& session) } User::pointer -User::getById(Wt::Dbo::Session& session, std::string id) +User::getById(Wt::Dbo::Session& session, id_type id) { return session.find().where("id = ?").bind( id ); } diff --git a/src/database/User.hpp b/src/database/User.hpp index 0bf1138a..de065e68 100644 --- a/src/database/User.hpp +++ b/src/database/User.hpp @@ -65,7 +65,7 @@ class User typedef Wt::Dbo::ptr pointer; // accessors - static pointer getById(Wt::Dbo::Session& session, std::string id); + static pointer getById(Wt::Dbo::Session& session, id_type id); static std::vector getAll(Wt::Dbo::Session& session); static std::string getId(pointer user); diff --git a/src/ui/ArtistsView.cpp b/src/ui/ArtistsView.cpp index c30ad77e..7373c39b 100644 --- a/src/ui/ArtistsView.cpp +++ b/src/ui/ArtistsView.cpp @@ -42,28 +42,24 @@ Artists::Artists(Filters* filters, Wt::WContainerWidget* parent) auto artists = new Wt::WTemplate(Wt::WString::tr("template-artists"), this); artists->addFunction("tr", &Wt::WTemplate::Functions::tr); - auto search = new Wt::WLineEdit(); - artists->bindWidget("search", search); - search->setPlaceholderText(Wt::WString::tr("msg-search-placeholder")); - search->textInput().connect(std::bind([this, search] - { - auto keywords = splitString(search->text().toUTF8(), " "); - refresh(keywords); - })); + _search = new Wt::WLineEdit(); + artists->bindWidget("search", _search); + _search->setPlaceholderText(Wt::WString::tr("msg-search-placeholder")); + _search->textInput().connect(this, &Artists::refresh); _artistsContainer = new Wt::WContainerWidget(); artists->bindWidget("artists", _artistsContainer); refresh(); - filters->updated().connect(std::bind([=] { - refresh(); - })); + filters->updated().connect(this, &Artists::refresh); } void -Artists::refresh(std::vector searchKeywords) +Artists::refresh() { + auto searchKeywords = splitString(_search->text().toUTF8(), " "); + _artistsContainer->clear(); auto clusterIds = _filters->getClusterIds(); diff --git a/src/ui/ArtistsView.hpp b/src/ui/ArtistsView.hpp index 74e07bab..cbf9c26a 100644 --- a/src/ui/ArtistsView.hpp +++ b/src/ui/ArtistsView.hpp @@ -20,6 +20,7 @@ #pragma once #include +#include #include namespace UserInterface { @@ -35,9 +36,10 @@ class Artists : public Wt::WContainerWidget Wt::Signal artistPlay; private: - void refresh(std::vector searchKeywords = std::vector()); + void refresh(); Filters* _filters; + Wt::WLineEdit* _search; Wt::WContainerWidget* _artistsContainer; }; diff --git a/src/ui/Explore.cpp b/src/ui/Explore.cpp index 20539133..d11df72a 100644 --- a/src/ui/Explore.cpp +++ b/src/ui/Explore.cpp @@ -126,11 +126,9 @@ Explore::Explore(Wt::WContainerWidget* parent) } // TODO SQL this? -static std::vector getArtistTracks(Wt::Dbo::Session& session, Database::id_type artistId, std::set clusters) +static std::vector getArtistTracks(Wt::Dbo::Session& session, Database::id_type artistId, std::set clusters) { - std::vector res; - - Wt::Dbo::Transaction transaction(session); + std::vector res; auto artist = Database::Artist::getById(session, artistId); if (!artist) @@ -140,31 +138,30 @@ static std::vector getArtistTracks(Wt::Dbo::Session& session, for (auto release : releases) { auto tracks = release->getTracks(clusters); - - for (auto track : tracks) - { - res.push_back(track.id()); - } + res.insert( res.end(), tracks.begin(), tracks.end() ); } return res; } -static std::vector getReleaseTracks(Wt::Dbo::Session& session, Database::id_type releaseId, std::set clusters) +static std::vector getReleaseTracks(Wt::Dbo::Session& session, Database::id_type releaseId, std::set clusters) { - std::vector res; - - Wt::Dbo::Transaction transaction(session); + std::vector res; auto release = Database::Release::getById(session, releaseId); if (!release) return res; - auto tracks = release->getTracks(clusters); - for (auto track : tracks) - { - res.push_back(track.id()); - } + return release->getTracks(clusters); +} + +static std::vector getTrack(Wt::Dbo::Session& session, Database::id_type trackId) +{ + std::vector res; + + auto track = Database::Track::getById(session, trackId); + if (track) + res.push_back(track); return res; } @@ -172,37 +169,49 @@ static std::vector getReleaseTracks(Wt::Dbo::Session& session void Explore::handleArtistAdd(Database::id_type id) { + Wt::Dbo::Transaction transaction(DboSession()); + tracksAdd.emit(getArtistTracks(DboSession(), id, _filters->getClusterIds())); } void Explore::handleArtistPlay(Database::id_type id) { + Wt::Dbo::Transaction transaction(DboSession()); + tracksPlay.emit(getArtistTracks(DboSession(), id, _filters->getClusterIds())); } void Explore::handleReleaseAdd(Database::id_type id) { + Wt::Dbo::Transaction transaction(DboSession()); + tracksAdd.emit(getReleaseTracks(DboSession(), id, _filters->getClusterIds())); } void Explore::handleReleasePlay(Database::id_type id) { + Wt::Dbo::Transaction transaction(DboSession()); + tracksPlay.emit(getReleaseTracks(DboSession(), id, _filters->getClusterIds())); } void Explore::handleTrackAdd(Database::id_type id) { - tracksAdd.emit(std::vector(1, id)); + Wt::Dbo::Transaction transaction(DboSession()); + + tracksAdd.emit(getTrack(DboSession(), id)); } void Explore::handleTrackPlay(Database::id_type id) { - tracksPlay.emit(std::vector(1, id)); + Wt::Dbo::Transaction transaction(DboSession()); + + tracksPlay.emit(getTrack(DboSession(), id)); } } // namespace UserInterface diff --git a/src/ui/Explore.hpp b/src/ui/Explore.hpp index c7857811..0e04a967 100644 --- a/src/ui/Explore.hpp +++ b/src/ui/Explore.hpp @@ -32,8 +32,8 @@ class Explore : public Wt::WContainerWidget public: Explore(Wt::WContainerWidget *parent = 0); - Wt::Signal> tracksAdd; - Wt::Signal> tracksPlay; + Wt::Signal> tracksAdd; + Wt::Signal> tracksPlay; private: diff --git a/src/ui/LmsApplication.cpp b/src/ui/LmsApplication.cpp index 51d0d665..ba1997ab 100644 --- a/src/ui/LmsApplication.cpp +++ b/src/ui/LmsApplication.cpp @@ -123,7 +123,8 @@ const Wt::Auth::User& CurrentAuthUser() Database::User::pointer CurrentUser() { - return DbHandler().getCurrentUser(); + return Database::User::getById(DboSession(), 1); +// return DbHandler().getCurrentUser(); } ImageResource* SessionImageResource() @@ -276,12 +277,12 @@ LmsApplication::handleAuthEvent(void) mainStack->addWidget(playlist); mainStack->addWidget(new Wt::WText("SETTINGS")); - explore->tracksAdd.connect(std::bind([=] (std::vector tracks) + explore->tracksAdd.connect(std::bind([=] (std::vector tracks) { playlist->addTracks(tracks); }, std::placeholders::_1)); - explore->tracksPlay.connect(std::bind([=] (std::vector tracks) + explore->tracksPlay.connect(std::bind([=] (std::vector tracks) { playlist->playTracks(tracks); }, std::placeholders::_1)); diff --git a/src/ui/PlaylistView.cpp b/src/ui/PlaylistView.cpp index 93f32f7a..57f8474c 100644 --- a/src/ui/PlaylistView.cpp +++ b/src/ui/PlaylistView.cpp @@ -17,13 +17,18 @@ * along with LMS. If not, see . */ +#include +#include #include #include +#include "LmsApplication.hpp" #include "PlaylistView.hpp" namespace UserInterface { +static const std::string currentPlaylistName = "__current__playlist__"; + Playlist::Playlist(Wt::WContainerWidget* parent) : Wt::WContainerWidget(parent) { @@ -41,26 +46,96 @@ Playlist::Playlist(Wt::WContainerWidget* parent) _entriesContainer = new Wt::WContainerWidget(); t->bindWidget("entries", _entriesContainer); + + refresh(); } void -Playlist::addTracks(const std::vector& trackIds) +Playlist::addTracks(const std::vector& tracks) { - for (auto trackId : trackIds) - std::cerr << "Adding track " << trackId << std::endl; + // Use a "session" playlist in order to store the current playlist + // so that the user can disconnect and get its playlist back + + auto playlist = Database::Playlist::get(DboSession(), currentPlaylistName, CurrentUser()); + if (!playlist) + { + playlist = Database::Playlist::create(DboSession(), currentPlaylistName, false, CurrentUser()); + } + + for (auto track : tracks) + { + playlist.modify()->addTrack(track); + } + + refresh(); } void -Playlist::playTracks(const std::vector& trackIds) +Playlist::playTracks(const std::vector& tracks) { - for (auto trackId : trackIds) - std::cerr << "Playing track " << trackId << std::endl; + Wt::Dbo::Transaction transaction(DboSession()); + + auto playlist = Database::Playlist::get(DboSession(), currentPlaylistName, CurrentUser()); + if (playlist) + playlist.modify()->clear(); + + _entriesContainer->clear(); + + addTracks(tracks); + + // TODO Immediate play } void Playlist::refresh() { + Wt::Dbo::Transaction transaction (DboSession()); + auto playlist = Database::Playlist::get(DboSession(), currentPlaylistName, CurrentUser()); + if (!playlist) + return; + + auto tracks = playlist->getTracks(_entriesContainer->count(), 20); // TODO + for (auto track : tracks) + { + Wt::WTemplate* entry = new Wt::WTemplate(Wt::WString::tr("template-playlist-entry"), _entriesContainer); + + entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText); + + auto artist = track->getArtist(); + if (artist) + { + entry->setCondition("if-has-artist", true); + Wt::WAnchor *artistAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/artist/" + std::to_string(track->getArtist().id()))); + Wt::WText *artistText = new Wt::WText(artistAnchor); + artistText->setText(Wt::WString::fromUTF8(artist->getName(), Wt::PlainText)); + entry->bindWidget("artist-name", artistAnchor); + } + auto release = track->getRelease(); + if (release) + { + entry->setCondition("if-has-release", true); + // TODO anchor + Wt::WAnchor *releaseAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(track->getRelease().id()))); + Wt::WText *releaseText = new Wt::WText(releaseAnchor); + releaseText->setText(Wt::WString::fromUTF8(release->getName(), Wt::PlainText)); + entry->bindWidget("release-name", releaseAnchor); + } + + Wt::WImage *cover = new Wt::WImage(); + cover->setImageLink(SessionImageResource()->getTrackUrl(track.id(), 64)); + // Some images may not be square + cover->setWidth(64); + entry->bindWidget("cover", cover); + + auto playBtn = new Wt::WText(Wt::WString::tr("btn-playlist-entry-play-btn"), Wt::XHTMLText); + entry->bindWidget("play-btn", playBtn); + // TODO + + auto addBtn = new Wt::WText(Wt::WString::tr("btn-playlist-entry-del-btn"), Wt::XHTMLText); + entry->bindWidget("del-btn", addBtn); + // TODO + } } } // namespace UserInterface diff --git a/src/ui/PlaylistView.hpp b/src/ui/PlaylistView.hpp index fa1f6863..7a0dc595 100644 --- a/src/ui/PlaylistView.hpp +++ b/src/ui/PlaylistView.hpp @@ -30,8 +30,8 @@ class Playlist : public Wt::WContainerWidget public: Playlist(Wt::WContainerWidget* parent = 0); - void addTracks(const std::vector& trackIds); - void playTracks(const std::vector& trackIds); + void addTracks(const std::vector& tracks); + void playTracks(const std::vector& tracks); private: void refresh(); diff --git a/src/ui/ReleasesView.cpp b/src/ui/ReleasesView.cpp index 9c1bfdf7..dce1b8eb 100644 --- a/src/ui/ReleasesView.cpp +++ b/src/ui/ReleasesView.cpp @@ -19,7 +19,6 @@ #include #include -#include #include #include #include @@ -44,29 +43,25 @@ Releases::Releases(Filters* filters, Wt::WContainerWidget* parent) auto releases = new Wt::WTemplate(Wt::WString::tr("template-releases"), this); releases->addFunction("tr", &Wt::WTemplate::Functions::tr); - auto search = new Wt::WLineEdit(); - releases->bindWidget("search", search); - search->setPlaceholderText(Wt::WString::tr("msg-search-placeholder")); - search->textInput().connect(std::bind([this, search] - { - auto keywords = splitString(search->text().toUTF8(), " "); - refresh(keywords); - })); + _search = new Wt::WLineEdit(); + releases->bindWidget("search", _search); + _search->setPlaceholderText(Wt::WString::tr("msg-search-placeholder")); + _search->textInput().connect(this, &Releases::refresh); _releasesContainer = new Wt::WContainerWidget(); releases->bindWidget("releases", _releasesContainer); refresh(); - filters->updated().connect(std::bind([=] { - refresh(); - })); + filters->updated().connect(this, &Releases::refresh); } void -Releases::refresh(std::vector searchKeywords) +Releases::refresh() { + auto searchKeywords = splitString(_search->text().toUTF8(), " "); + _releasesContainer->clear(); auto clusterIds = _filters->getClusterIds(); diff --git a/src/ui/ReleasesView.hpp b/src/ui/ReleasesView.hpp index 7cae344d..9bc9c996 100644 --- a/src/ui/ReleasesView.hpp +++ b/src/ui/ReleasesView.hpp @@ -20,6 +20,7 @@ #pragma once #include +#include namespace UserInterface { @@ -34,9 +35,10 @@ class Releases : public Wt::WContainerWidget Wt::Signal releasePlay; private: - void refresh(std::vector searchKeywords = std::vector()); + void refresh(); Filters* _filters; + Wt::WLineEdit* _search; Wt::WContainerWidget* _releasesContainer; }; diff --git a/src/ui/TracksView.cpp b/src/ui/TracksView.cpp index 24e8fe54..a4414f2f 100644 --- a/src/ui/TracksView.cpp +++ b/src/ui/TracksView.cpp @@ -43,29 +43,25 @@ Tracks::Tracks(Filters* filters, Wt::WContainerWidget* parent) auto tracks = new Wt::WTemplate(Wt::WString::tr("template-tracks"), this); tracks->addFunction("tr", &Wt::WTemplate::Functions::tr); - auto search = new Wt::WLineEdit(); - tracks->bindWidget("search", search); - search->setPlaceholderText(Wt::WString::tr("msg-search-placeholder")); - search->textInput().connect(std::bind([this, search] - { - auto keywords = splitString(search->text().toUTF8(), " "); - refresh(keywords); - })); + _search = new Wt::WLineEdit(); + tracks->bindWidget("search", _search); + _search->setPlaceholderText(Wt::WString::tr("msg-search-placeholder")); + _search->textInput().connect(this, &Tracks::refresh); _tracksContainer = new Wt::WContainerWidget(); tracks->bindWidget("tracks", _tracksContainer); refresh(); - filters->updated().connect(std::bind([=] { - refresh(); - })); + filters->updated().connect(this, &Tracks::refresh); } void -Tracks::refresh(std::vector searchKeywords) +Tracks::refresh() { + auto searchKeywords = splitString(_search->text().toUTF8(), " "); + _tracksContainer->clear(); auto clusterIds = _filters->getClusterIds(); diff --git a/src/ui/TracksView.hpp b/src/ui/TracksView.hpp index 516e4f9b..4f82cfe9 100644 --- a/src/ui/TracksView.hpp +++ b/src/ui/TracksView.hpp @@ -20,6 +20,7 @@ #pragma once #include +#include namespace UserInterface { @@ -33,9 +34,10 @@ class Tracks : public Wt::WContainerWidget Wt::Signal trackPlay; private: - void refresh(std::vector searchKeywords = std::vector()); + void refresh(); Wt::WContainerWidget* _tracksContainer; + Wt::WLineEdit* _search; Filters* _filters; };