/* * Copyright (C) 2014 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 "database/TrackList.hpp" #include #include "utils/Logger.hpp" #include "database/Artist.hpp" #include "database/Cluster.hpp" #include "database/Release.hpp" #include "database/Session.hpp" #include "database/User.hpp" #include "database/Track.hpp" #include "SqlQuery.hpp" #include "StringViewTraits.hpp" namespace Database { TrackList::TrackList(std::string_view name, Type type, bool isPublic, Wt::Dbo::ptr user) : _name {name}, _type {type}, _isPublic {isPublic}, _user {user} { } TrackList::pointer TrackList::create(Session& session, std::string_view name, Type type, bool isPublic, Wt::Dbo::ptr user) { session.checkUniqueLocked(); assert(user); auto res = session.getDboSession().add( std::make_unique(name, type, isPublic, user) ); session.getDboSession().flush(); return res; } TrackList::pointer TrackList::get(Session& session, std::string_view name, Type type, Wt::Dbo::ptr user) { session.checkSharedLocked(); assert(user); return session.getDboSession().find() .where("name = ?").bind(name) .where("type = ?").bind(type) .where("user_id = ?").bind(user.id()); } std::vector TrackList::getAll(Session& session) { session.checkSharedLocked(); Wt::Dbo::collection res = session.getDboSession().find(); return std::vector(res.begin(), res.end()); } std::vector TrackList::getAll(Session& session, Wt::Dbo::ptr user) { session.checkSharedLocked(); Wt::Dbo::collection res = session.getDboSession().find() .where("user_id = ?").bind(user.id()) .orderBy("name COLLATE NOCASE"); return std::vector(res.begin(), res.end()); } std::vector TrackList::getAll(Session& session, Wt::Dbo::ptr user, Type type) { session.checkSharedLocked(); Wt::Dbo::collection res = session.getDboSession().find() .where("user_id = ?").bind(user.id()) .where("type = ?").bind(type) .orderBy("name COLLATE NOCASE"); return std::vector(res.begin(), res.end()); } TrackList::pointer TrackList::getById(Session& session, IdType id) { session.checkSharedLocked(); return session.getDboSession().find().where("id = ?").bind(id); } bool TrackList::isEmpty() const { return _entries.empty(); } std::size_t TrackList::getCount() const { return _entries.size(); } Wt::Dbo::ptr TrackList::getEntry(std::size_t pos) const { Wt::Dbo::ptr res; auto entries = getEntries(pos, 1); if (!entries.empty()) res = entries.front(); return res; } std::vector> TrackList::getEntries(std::optional offset, std::optional size) const { assert(session()); assert(IdIsValid(self()->id())); Wt::Dbo::collection> entries = session()->find() .where("tracklist_id = ?").bind(self().id()) .orderBy("id") .limit(size ? static_cast(*size) : -1) .offset(offset ? static_cast(*offset) : -1); return std::vector>(entries.begin(), entries.end()); } static Wt::Dbo::Query createArtistsQuery(Wt::Dbo::Session& session, const std::string& queryStr, IdType tracklistId, const std::set& clusterIds, std::optional linkType) { auto query {session.query(queryStr)}; query.join("track t ON t.id = t_a_l.track_id"); query.join("track_artist_link t_a_l ON t_a_l.artist_id = a.id"); query.join("tracklist_entry p_e ON p_e.track_id = t.id"); query.join("tracklist p ON p.id = p_e.tracklist_id"); query.where("p.id = ?").bind(tracklistId); if (linkType) query.where("t_a_l.type = ?").bind(*linkType); if (!clusterIds.empty()) { std::ostringstream oss; oss << "a.id IN (SELECT DISTINCT a.id FROM artist a" " INNER JOIN track t ON t.id = t_a_l.track_id" " INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id" " INNER JOIN cluster c ON c.id = t_c.cluster_id" " INNER JOIN track_cluster t_c ON t_c.track_id = t.id"; WhereClause clusterClause; for (auto id : clusterIds) { clusterClause.Or(WhereClause("c.id = ?")); query.bind(id); } oss << " " << clusterClause.get(); oss << " GROUP BY t.id,a.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size() << ")"; query.where(oss.str()); } return query; } static Wt::Dbo::Query createReleasesQuery(Wt::Dbo::Session& session, const std::string& queryStr, IdType tracklistId, const std::set& clusterIds) { auto query {session.query(queryStr)}; query.join("track t ON t.release_id = r.id"); query.join("tracklist_entry p_e ON p_e.track_id = t.id"); query.join("tracklist p ON p.id = p_e.tracklist_id"); query.where("p.id = ?").bind(tracklistId); if (!clusterIds.empty()) { std::ostringstream oss; oss << "r.id IN (SELECT DISTINCT r.id FROM release r" " INNER JOIN track t ON t.release_id = r.id" " INNER JOIN cluster c ON c.id = t_c.cluster_id" " INNER JOIN track_cluster t_c ON t_c.track_id = t.id"; WhereClause clusterClause; for (auto id : clusterIds) { clusterClause.Or(WhereClause("c.id = ?")); query.bind(id); } oss << " " << clusterClause.get(); oss << " GROUP BY t.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size() << ")"; query.where(oss.str()); } return query; } static Wt::Dbo::Query createTracksQuery(Wt::Dbo::Session& session, IdType tracklistId, const std::set& clusterIds) { auto query {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")}; query.where("p.id = ?").bind(tracklistId); if (!clusterIds.empty()) { std::ostringstream oss; oss << "t.id IN (SELECT DISTINCT t.id FROM track t" " INNER JOIN track_cluster t_c ON t_c.track_id = t.id" " INNER JOIN cluster c ON c.id = t_c.cluster_id"; WhereClause clusterClause; for (auto id : clusterIds) { clusterClause.Or(WhereClause("c.id = ?")).bind(std::to_string(id)); query.bind(id); } oss << " " << clusterClause.get(); oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size() << ")"; query.where(oss.str()); } return query; } std::vector TrackList::getArtistsReverse(const std::set& clusterIds, std::optional linkType, std::optional range, bool& moreResults) const { assert(session()); assert(IdIsValid(self()->id())); Wt::Dbo::collection collection = createArtistsQuery(*session(), "SELECT a from artist a", self()->id(), clusterIds, linkType) .groupBy("a.id").having("p_e.date_time = MAX(p_e.date_time)") .orderBy("p_e.date_time DESC") .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 res; } std::vector TrackList::getReleasesReverse(const std::set& clusterIds, std::optional range, bool& moreResults) const { assert(session()); assert(IdIsValid(self()->id())); Wt::Dbo::collection collection = createReleasesQuery(*session(), "SELECT r from release r", self()->id(), clusterIds) .groupBy("r.id").having("p_e.date_time = MAX(p_e.date_time)") .orderBy("p_e.date_time DESC") .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 res; } std::vector TrackList::getTracksReverse(const std::set& clusterIds, std::optional range, bool& moreResults) const { assert(session()); assert(IdIsValid(self()->id())); Wt::Dbo::collection collection = createTracksQuery(*session(), self()->id(), clusterIds) .groupBy("t.id").having("p_e.date_time = MAX(p_e.date_time)") .orderBy("p_e.date_time DESC") .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 res; } std::vector> TrackList::getClusters() const { assert(session()); assert(IdIsValid(self()->id())); Wt::Dbo::collection res = session()->query("SELECT c from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id 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("c.id") .orderBy("COUNT(c.id) DESC"); return std::vector>(res.begin(), res.end()); } bool TrackList::hasTrack(IdType trackId) const { assert(session()); assert(IdIsValid(self()->id())); 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(self()->id()); return res.size() > 0; } std::vector TrackList::getSimilarTracks(std::optional offset, std::optional size) const { assert(session()); assert(IdIsValid(self()->id())); Wt::Dbo::Query query {session()->query( "SELECT t FROM track t" " INNER JOIN track_cluster t_c ON t_c.track_id = t.id" " WHERE " " (t_c.cluster_id IN (SELECT c.id from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id 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 = ?)" " AND t.id NOT IN (SELECT tracklist_t.id FROM track tracklist_t INNER JOIN tracklist_entry t_e ON t_e.track_id = tracklist_t.id WHERE t_e.tracklist_id = ?))" ) .bind(self()->id()) .bind(self()->id()) .groupBy("t.id") .orderBy("COUNT(*) DESC, RANDOM()") .limit(size ? static_cast(*size) : -1) .offset(offset ? static_cast(*offset) : -1)}; Wt::Dbo::collection tracks = query; return std::vector(tracks.begin(), tracks.end()); } std::vector TrackList::getTrackIds() const { assert(session()); assert(IdIsValid(self()->id())); Wt::Dbo::collection res = session()->query("SELECT p_e.track_id from tracklist_entry p_e INNER JOIN tracklist p ON p_e.tracklist_id = p.id") .where("p.id = ?").bind(self()->id()); return std::vector(res.begin(), res.end()); } std::chrono::milliseconds TrackList::getDuration() const { assert(session()); assert(IdIsValid(self()->id())); using milli = std::chrono::duration; Wt::Dbo::Query query {session()->query("SELECT COALESCE(SUM(duration), 0) FROM track t INNER JOIN tracklist_entry p_e ON t.id = p_e.track_id") .where("p_e.tracklist_id = ?").bind(self()->id())}; return query.resultValue(); } std::vector TrackList::getTopArtists(const std::set& clusterIds, std::optional linkType, std::optional range, bool& moreResults) const { assert(session()); assert(IdIsValid(self()->id())); auto query {createArtistsQuery(*session(), "SELECT a from artist a", self()->id(), clusterIds, linkType)}; Wt::Dbo::collection collection = query .orderBy("COUNT(a.id) DESC") .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 res; } std::vector TrackList::getTopReleases(const std::set& clusterIds, std::optional range, bool& moreResults) const { assert(session()); assert(IdIsValid(self()->id())); auto query {createReleasesQuery(*session(), "SELECT r from release r", self()->id(), clusterIds)}; Wt::Dbo::collection collection = query .orderBy("COUNT(r.id) DESC") .groupBy("r.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 res; } std::vector TrackList::getTopTracks(const std::set& clusterIds, std::optional range, bool& moreResults) const { assert(session()); assert(IdIsValid(self()->id())); auto query {createTracksQuery(*session(), self()->id(), clusterIds)}; Wt::Dbo::collection collection = query .orderBy("COUNT(t.id) DESC") .groupBy("t.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 res; } TrackListEntry::TrackListEntry(Wt::Dbo::ptr track, Wt::Dbo::ptr tracklist, const Wt::WDateTime& dateTime) : _dateTime {Wt::WDateTime::fromTime_t(dateTime.toTime_t())} // force second resolution , _track {track} , _tracklist {tracklist} { assert(_dateTime.isValid()); } TrackListEntry::pointer TrackListEntry::create(Session& session, Wt::Dbo::ptr track, Wt::Dbo::ptr tracklist, const Wt::WDateTime& dateTime) { session.checkUniqueLocked(); assert(track); assert(tracklist); auto res = session.getDboSession().add(std::make_unique( track, tracklist, dateTime)); session.getDboSession().flush(); return res; } TrackListEntry::pointer TrackListEntry::getById(Session& session, IdType id) { session.checkSharedLocked(); return session.getDboSession().find().where("id = ?").bind(id); } } // namespace Database