/* * 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 "database/Cluster.hpp" #include "database/PlayListFile.hpp" #include "database/Session.hpp" #include "database/Track.hpp" #include "database/User.hpp" #include "SqlQuery.hpp" #include "Utils.hpp" #include "traits/IdTypeTraits.hpp" #include "traits/StringViewTraits.hpp" namespace lms::db { namespace { template Wt::Dbo::Query createQuery(Session& session, std::string_view itemToSelect, const TrackList::FindParameters& params) { assert(!params.user.isValid() || !params.excludedUser.isValid()); auto query{ session.getDboSession()->query("SELECT " + std::string{ itemToSelect } + " FROM tracklist t_l") }; if (!params.filters.clusters.empty() || params.filters.mediaLibrary.isValid() || params.filters.label.isValid()) { query.join("tracklist_entry t_l_e ON t_l_e.tracklist_id = t_l.id"); query.groupBy("t_l.id"); } for (std::string_view keyword : params.keywords) query.where("t_l.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%" + utils::escapeLikeKeyword(keyword) + "%"); if (params.filters.mediaLibrary.isValid() || params.filters.label.isValid() || params.filters.releaseType.isValid()) { query.join("track t ON t.id = t_l_e.track_id"); if (params.filters.mediaLibrary.isValid()) query.where("t.media_library_id = ?").bind(params.filters.mediaLibrary); if (params.filters.label.isValid()) { query.join("release_label r_l ON r_l.release_id = t.release_id"); query.where("r_l.label_id = ?").bind(params.filters.label); } if (params.filters.releaseType.isValid()) { query.join("release_release_type r_r_t ON r_r_t.release_id = t.release_id"); query.where("r_r_t.release_type_id = ?").bind(params.filters.releaseType); } } if (params.user.isValid()) query.where("t_l.user_id = ?").bind(params.user); else if (params.excludedUser.isValid()) query.where("t_l.user_id <> ? OR t_l.user_id IS NULL").bind(params.excludedUser); if (params.type) query.where("t_l.type = ?").bind(*params.type); if (params.visibility) query.where("t_l.visibility = ?").bind(*params.visibility); if (!params.filters.clusters.empty()) { std::ostringstream oss; oss << "t_l_e.track_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 (const ClusterId clusterId : params.filters.clusters) { clusterClause.Or(WhereClause("c.id = ?")); query.bind(clusterId); } oss << " " << clusterClause.get(); oss << " GROUP BY t.id HAVING COUNT(*) = " << params.filters.clusters.size() << ")"; query.where(oss.str()); } switch (params.sortMethod) { case TrackListSortMethod::None: break; case TrackListSortMethod::Name: query.orderBy("t_l.name COLLATE NOCASE"); break; case TrackListSortMethod::LastModifiedDesc: query.orderBy("t_l.last_modified_date_time DESC"); break; } return query; } template Wt::Dbo::Query createQuery(Session& session, const TrackList::FindParameters& params) { std::string_view itemToSelect; if constexpr (std::is_same_v) itemToSelect = "t_l.id"; else if constexpr (std::is_same_v>) itemToSelect = "t_l"; else static_assert("Unhandled type"); return createQuery(session, itemToSelect, params); } } // namespace TrackList::TrackList(std::string_view name, TrackListType type) : _name{ name } , _type{ type } , _creationDateTime{ utils::normalizeDateTime(Wt::WDateTime::currentDateTime()) } , _lastModifiedDateTime{ utils::normalizeDateTime(Wt::WDateTime::currentDateTime()) } { } TrackList::pointer TrackList::create(Session& session, std::string_view name, TrackListType type) { return session.getDboSession()->add(std::unique_ptr{ new TrackList{ name, type } }); } std::size_t TrackList::getCount(Session& session) { session.checkReadTransaction(); return utils::fetchQuerySingleResult(session.getDboSession()->query("SELECT COUNT(*) FROM tracklist")); } TrackList::pointer TrackList::find(Session& session, std::string_view name, TrackListType type, UserId userId) { session.checkReadTransaction(); assert(userId.isValid()); return utils::fetchQuerySingleResult(session.getDboSession()->query>("select t_l from tracklist t_l").where("t_l.name = ?").bind(name).where("t_l.type = ?").bind(type).where("t_l.user_id = ?").bind(userId)); } RangeResults TrackList::find(Session& session, const FindParameters& params) { session.checkReadTransaction(); auto query{ createQuery(session, params) }; return utils::execRangeQuery(query, params.range); } void TrackList::find(Session& session, const FindParameters& params, const std::function& func) { session.checkReadTransaction(); auto query{ createQuery>(session, params) }; utils::forEachQueryRangeResult(query, params.range, func); } TrackList::pointer TrackList::find(Session& session, TrackListId id) { session.checkReadTransaction(); return utils::fetchQuerySingleResult(session.getDboSession()->query>("select t_l from tracklist t_l").where("t_l.id = ?").bind(id)); } bool TrackList::isEmpty() const { return _entries.empty(); } std::size_t TrackList::getCount() const { return _entries.size(); } TrackListEntry::pointer TrackList::getEntry(std::size_t pos) const { TrackListEntry::pointer res; auto entries = getEntries(Range{ pos, 1 }); if (!entries.results.empty()) res = entries.results.front(); return res; } RangeResults> TrackList::getEntries(std::optional range) const { assert(session()); auto query{ session()->find().where("tracklist_id = ?").bind(getId()).orderBy("id") }; return utils::execRangeQuery(query, range); } TrackListEntry::pointer TrackList::getEntryByTrackAndDateTime(ObjectPtr track, const Wt::WDateTime& dateTime) const { assert(session()); return utils::fetchQuerySingleResult(session()->find().where("tracklist_id = ?").bind(getId()).where("track_id = ?").bind(track->getId()).where("date_time = ?").bind(utils::normalizeDateTime(dateTime))); } std::vector TrackList::getClusters() const { assert(session()); const auto query{ 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(getId()).groupBy("c.id").orderBy("COUNT(c.id) DESC") }; return utils::fetchQueryResults(query); } std::vector> TrackList::getClusterGroups(const std::vector& clusterTypeIds, std::size_t size) const { assert(session()); std::vector> res; if (clusterTypeIds.empty()) return res; auto query{ session()->query>("SELECT c from cluster c") }; query.join("track t ON c.id = t_c.cluster_id") .join("track_cluster t_c ON t_c.track_id = t.id") .join("cluster_type c_type ON c.cluster_type_id = c_type.id") .join("tracklist_entry t_l_e ON t_l_e.track_id = t.id") .join("tracklist t_l ON t_l.id = t_l_e.tracklist_id") .where("t_l.id = ?") .bind(getId()); { std::ostringstream oss; oss << "c_type.id IN ("; bool first{ true }; for (ClusterTypeId clusterTypeId : clusterTypeIds) { if (!first) oss << ", "; oss << "?"; query.bind(clusterTypeId); first = false; } oss << ")"; query.where(oss.str()); } query.groupBy("c.id"); query.orderBy("COUNT(c.id) DESC"); std::map> clustersByType; utils::forEachQueryResult(query, [&](const Cluster::pointer& cluster) { if (clustersByType[cluster->getType()->getId()].size() < size) clustersByType[cluster->getType()->getId()].push_back(cluster); }); for (const auto& [clusterTypeId, clusters] : clustersByType) res.push_back(clusters); return res; } std::vector TrackList::getSimilarTracks(std::optional offset, std::optional size) const { assert(session()); auto 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 DISTINCT 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(getId()) .bind(getId()) .groupBy("t.id") .orderBy("COUNT(*) DESC, RANDOM()") .limit(size ? static_cast(*size) : -1) .offset(offset ? static_cast(*offset) : -1) }; return utils::fetchQueryResults(query); } std::vector TrackList::getTrackIds() const { assert(session()); auto query{ 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(getId()) }; return utils::fetchQueryResults(query); } std::chrono::milliseconds TrackList::getDuration() const { assert(session()); using milli = std::chrono::duration; return utils::fetchQuerySingleResult(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(getId())); } void TrackList::setLastModifiedDateTime(const Wt::WDateTime& dateTime) { _lastModifiedDateTime = utils::normalizeDateTime(dateTime); } TrackListEntry::TrackListEntry(ObjectPtr track, ObjectPtr tracklist, const Wt::WDateTime& dateTime) : _dateTime{ utils::normalizeDateTime(dateTime) } , _track{ getDboPtr(track) } , _tracklist{ getDboPtr(tracklist) } { assert(track); assert(tracklist); } TrackListEntry::pointer TrackListEntry::create(Session& session, ObjectPtr track, ObjectPtr tracklist, const Wt::WDateTime& dateTime) { return session.getDboSession()->add(std::unique_ptr{ new TrackListEntry{ track, tracklist, dateTime } }); } TrackListEntry::pointer TrackListEntry::getById(Session& session, TrackListEntryId id) { session.checkReadTransaction(); return utils::fetchQuerySingleResult(session.getDboSession()->find().where("id = ?").bind(id)); } void TrackListEntry::find(Session& session, const FindParameters& params, const std::function& func) { session.checkReadTransaction(); auto query{ session.getDboSession()->query>("SELECT t_l_e FROM tracklist_entry t_l_e") }; if (params.trackList.isValid()) query.where("t_l_e.tracklist_id = ?").bind(params.trackList); query.orderBy("t_l_e.id"); utils::forEachQueryRangeResult(query, params.range, func); } } // namespace lms::db