From 71b17a858825572e6ab0b43ae1b24ea9ecbc6de2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-Xavier=20Thomas?= Date: Sun, 6 Nov 2016 13:10:17 +0100 Subject: [PATCH] Handle additional shuffle filters This commit also replaces Lucene with an SQL query when looking for random songs. This should improve performance quite a bit and allows us to use more complex filters on columns that are not indexed by Lucene. --- .../controller/RandomPlayQueueController.java | 157 +++++++++++++++--- .../libresonic/player/dao/MediaFileDao.java | 98 ++++++++++- .../player/service/MediaFileService.java | 20 ++- .../webapp/WEB-INF/libresonic-servlet.xml | 2 +- 4 files changed, 243 insertions(+), 34 deletions(-) diff --git a/libresonic-main/src/main/java/org/libresonic/player/controller/RandomPlayQueueController.java b/libresonic-main/src/main/java/org/libresonic/player/controller/RandomPlayQueueController.java index 6c813199..42e2687b 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/controller/RandomPlayQueueController.java +++ b/libresonic-main/src/main/java/org/libresonic/player/controller/RandomPlayQueueController.java @@ -19,30 +19,21 @@ */ package org.libresonic.player.controller; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.text.NumberFormat; +import java.util.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.StringUtils; +import org.apache.taglibs.standard.lang.jstl.IntegerDivideOperator; +import org.libresonic.player.domain.*; +import org.libresonic.player.service.*; import org.springframework.web.bind.ServletRequestBindingException; import org.springframework.web.bind.ServletRequestUtils; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.ParameterizableViewController; -import org.libresonic.player.domain.MusicFolder; -import org.libresonic.player.domain.PlayQueue; -import org.libresonic.player.domain.Player; -import org.libresonic.player.domain.RandomSearchCriteria; -import org.libresonic.player.service.PlayerService; -import org.libresonic.player.service.SearchService; -import org.libresonic.player.service.SecurityService; -import org.libresonic.player.service.SettingsService; - /** * Controller for the creating a random play queue. * @@ -52,21 +43,31 @@ public class RandomPlayQueueController extends ParameterizableViewController { private PlayerService playerService; private List reloadFrames; - private SearchService searchService; + private MediaFileService mediaFileService; private SecurityService securityService; private SettingsService settingsService; protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { int size = ServletRequestUtils.getRequiredIntParameter(request, "size"); - String genre = request.getParameter("genre"); - if (StringUtils.equalsIgnoreCase("any", genre)) { - genre = null; - } + String genre; Integer fromYear = null; Integer toYear = null; + Integer minAlbumRating = null; + Integer maxAlbumRating = null; + Date minLastPlayedDate = null; + Date maxLastPlayedDate = null; + boolean doesShowStarredSongs = false; + boolean doesShowUnstarredSongs = false; + + // Handle the genre filter + genre = request.getParameter("genre"); + if (StringUtils.equalsIgnoreCase("any", genre)) { + genre = null; + } + // Handle the release year filter String year = request.getParameter("year"); if (!StringUtils.equalsIgnoreCase("any", year)) { String[] tmp = StringUtils.split(year); @@ -74,12 +75,116 @@ public class RandomPlayQueueController extends ParameterizableViewController { toYear = Integer.parseInt(tmp[1]); } + // Handle the song rating filter + String songRating = request.getParameter("songRating"); + if (StringUtils.equalsIgnoreCase("any", songRating)) { + doesShowStarredSongs = true; + doesShowUnstarredSongs = true; + } else if (StringUtils.equalsIgnoreCase("starred", songRating)) { + doesShowStarredSongs = true; + doesShowUnstarredSongs = false; + } else if (StringUtils.equalsIgnoreCase("unstarred", songRating)) { + doesShowStarredSongs = false; + doesShowUnstarredSongs = true; + } + + // Handle the last played date filter + String lastPlayedValue = request.getParameter("lastPlayedValue"); + String lastPlayedComp = request.getParameter("lastPlayedComp"); + Calendar lastPlayed = Calendar.getInstance(); + lastPlayed.setTime(new Date()); + switch (lastPlayedValue) { + case "any": + lastPlayed = null; + break; + case "1day": + lastPlayed.add(Calendar.DAY_OF_YEAR, -1); + break; + case "1week": + lastPlayed.add(Calendar.WEEK_OF_YEAR, -1); + break; + case "1month": + lastPlayed.add(Calendar.MONTH, -1); + break; + case "3months": + lastPlayed.add(Calendar.MONTH, -3); + break; + case "6months": + lastPlayed.add(Calendar.MONTH, -6); + break; + case "1year": + lastPlayed.add(Calendar.YEAR, -1); + break; + } + if (lastPlayed != null) { + switch (lastPlayedComp) { + case "lt": + minLastPlayedDate = null; + maxLastPlayedDate = lastPlayed.getTime(); + break; + case "gt": + minLastPlayedDate = lastPlayed.getTime(); + maxLastPlayedDate = null; + break; + } + } + + // Handle the album rating filter + Integer albumRatingValue = null; + try { albumRatingValue = Integer.parseInt(request.getParameter("albumRatingValue")); } + catch (NumberFormatException e) { } + String albumRatingComp = request.getParameter("albumRatingComp"); + if (albumRatingValue != null) { + switch (albumRatingComp) { + case "lt": + minAlbumRating = null; + maxAlbumRating = albumRatingValue - 1; + break; + case "gt": + minAlbumRating = albumRatingValue + 1; + maxAlbumRating = null; + break; + case "le": + minAlbumRating = null; + maxAlbumRating = albumRatingValue; + break; + case "ge": + minAlbumRating = albumRatingValue; + maxAlbumRating = null; + break; + case "eq": + minAlbumRating = albumRatingValue; + maxAlbumRating = albumRatingValue; + break; + } + } + + // Handle the format filter + String format = request.getParameter("format"); + if (StringUtils.equalsIgnoreCase(format, "any")) format = null; + + // Handle the music folder filter List musicFolders = getMusicFolders(request); + + // Search the database using these criteria + RandomSearchCriteria criteria = new RandomSearchCriteria( + size, + genre, + fromYear, + toYear, + musicFolders, + minLastPlayedDate, + maxLastPlayedDate, + minAlbumRating, + maxAlbumRating, + doesShowStarredSongs, + doesShowUnstarredSongs, + format + ); + User user = securityService.getCurrentUser(request); Player player = playerService.getPlayer(request, response); PlayQueue playQueue = player.getPlayQueue(); - - RandomSearchCriteria criteria = new RandomSearchCriteria(size, genre, fromYear, toYear, musicFolders); - playQueue.addFiles(false, searchService.getRandomSongs(criteria)); + playQueue.addFiles(false, mediaFileService.getRandomSongs(criteria, user.getUsername())); if (request.getParameter("autoRandom") != null) { playQueue.setRandomSearchCriteria(criteria); @@ -110,10 +215,6 @@ public class RandomPlayQueueController extends ParameterizableViewController { this.reloadFrames = reloadFrames; } - public void setSearchService(SearchService searchService) { - this.searchService = searchService; - } - public void setSecurityService(SecurityService securityService) { this.securityService = securityService; } @@ -121,4 +222,8 @@ public class RandomPlayQueueController extends ParameterizableViewController { public void setSettingsService(SettingsService settingsService) { this.settingsService = settingsService; } + + public void setMediaFileService(MediaFileService mediaFileService) { + this.mediaFileService = mediaFileService; + } } diff --git a/libresonic-main/src/main/java/org/libresonic/player/dao/MediaFileDao.java b/libresonic-main/src/main/java/org/libresonic/player/dao/MediaFileDao.java index 78dd0b47..d6b9dd7d 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/dao/MediaFileDao.java +++ b/libresonic-main/src/main/java/org/libresonic/player/dao/MediaFileDao.java @@ -20,10 +20,12 @@ package org.libresonic.player.dao; import org.apache.commons.lang.StringUtils; +import org.libresonic.player.domain.RandomSearchCriteria; +import org.springframework.jdbc.core.RowMapper; + import org.libresonic.player.domain.Genre; import org.libresonic.player.domain.MediaFile; import org.libresonic.player.domain.MusicFolder; -import org.springframework.jdbc.core.RowMapper; import java.sql.ResultSet; import java.sql.SQLException; @@ -480,6 +482,100 @@ public class MediaFileDao extends AbstractDao { rowMapper, args); } + public List getRandomSongs(RandomSearchCriteria criteria, final String username) { + if (criteria.getMusicFolders().isEmpty()) { + return Collections.emptyList(); + } + + Map args = new HashMap() {{ + put("folders", MusicFolder.toPathList(criteria.getMusicFolders())); + put("username", username); + put("count", criteria.getCount()); + put("fromYear", criteria.getFromYear()); + put("toYear", criteria.getToYear()); + put("genre", criteria.getGenre()); + put("minLastPlayed", criteria.getMinLastPlayedDate()); + put("maxLastPlayed", criteria.getMaxLastPlayedDate()); + put("minAlbumRating", criteria.getMinAlbumRating()); + put("maxAlbumRating", criteria.getMaxAlbumRating()); + put("starred", criteria.isShowStarredSongs()); + put("unstarred", criteria.isShowUnstarredSongs()); + put("format", criteria.getFormat()); + }}; + + boolean joinAlbumRating = (criteria.getMinAlbumRating() != null || criteria.getMaxAlbumRating() != null); + boolean joinStarred = (criteria.isShowStarredSongs() ^ criteria.isShowUnstarredSongs()); + + String query = "select top :count " + prefix(COLUMNS, "media_file") + " from media_file "; + + if (joinStarred) { + query += "left outer join starred_media_file on media_file.id = starred_media_file.media_file_id and starred_media_file.username = :username "; + } + + if (joinAlbumRating) { + query += "left outer join media_file media_album on media_album.type = 'ALBUM' and media_album.album = media_file.album and media_album.artist = media_file.artist "; + query += "left outer join user_rating on user_rating.path = media_album.path and user_rating.username = :username "; + } + + query += " where media_file.present and media_file.type = 'MUSIC'"; + + if (!criteria.getMusicFolders().isEmpty()) { + query += " and media_file.folder in (:folders)"; + } + + if (criteria.getGenre() != null) { + query += " and media_file.genre = :genre"; + } + + if (criteria.getFormat() != null) { + query += " and media_file.format = :format"; + } + + if (criteria.getFromYear() != null) { + query += " and media_file.year >= :fromYear"; + } + + if (criteria.getToYear() != null) { + query += " and media_file.year <= :toYear"; + } + + if (criteria.getMinLastPlayedDate() != null) { + query += " and media_file.last_played >= :minLastPlayed"; + } + + if (criteria.getMaxLastPlayedDate() != null) { + if (criteria.getMinLastPlayedDate() == null) { + query += " and (media_file.last_played is null or media_file.last_played <= :maxLastPlayed)"; + } else { + query += " and media_file.last_played <= :maxLastPlayed"; + } + } + + if (criteria.getMinAlbumRating() != null) { + query += " and user_rating.rating >= :minAlbumRating"; + } + + if (criteria.getMaxAlbumRating() != null) { + if (criteria.getMinAlbumRating() == null) { + query += " and (user_rating.rating is null or user_rating.rating <= :maxAlbumRating)"; + } else { + query += " and user_rating.rating <= :maxAlbumRating"; + } + } + + if (criteria.isShowStarredSongs() && !criteria.isShowUnstarredSongs()) { + query += " and starred_media_file.id is not null"; + } + + if (criteria.isShowUnstarredSongs() && !criteria.isShowStarredSongs()) { + query += " and starred_media_file.id is null"; + } + + query += " order by rand()"; + + return namedQuery(query, rowMapper, args); + } + public int getAlbumCount(final List musicFolders) { if (musicFolders.isEmpty()) { return 0; diff --git a/libresonic-main/src/main/java/org/libresonic/player/service/MediaFileService.java b/libresonic-main/src/main/java/org/libresonic/player/service/MediaFileService.java index 27e755b9..5e59832c 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/service/MediaFileService.java +++ b/libresonic-main/src/main/java/org/libresonic/player/service/MediaFileService.java @@ -41,11 +41,7 @@ import net.sf.ehcache.Element; import org.libresonic.player.Logger; import org.libresonic.player.dao.AlbumDao; import org.libresonic.player.dao.MediaFileDao; -import org.libresonic.player.domain.Album; -import org.libresonic.player.domain.Genre; -import org.libresonic.player.domain.MediaFile; -import org.libresonic.player.domain.MediaFileComparator; -import org.libresonic.player.domain.MusicFolder; +import org.libresonic.player.domain.*; import org.libresonic.player.service.metadata.JaudiotaggerParser; import org.libresonic.player.service.metadata.MetaData; import org.libresonic.player.service.metadata.MetaDataParser; @@ -331,7 +327,7 @@ public class MediaFileService { } /** - * Returns random songs for the give parent. + * Returns random songs for the given parent. * * @param parent The parent. * @param count Max number of songs to return. @@ -348,6 +344,18 @@ public class MediaFileService { return children.subList(0, Math.min(count, children.size())); } + /** + * Returns random songs matching search criteria. + * + * @param criteria Random search criteria. + * @param count Max number of songs to return. + * @return Random songs + * @see SearchService.getRandomSongs + */ + public List getRandomSongs(RandomSearchCriteria criteria, String username) { + return mediaFileDao.getRandomSongs(criteria, username); + } + /** * Removes video files from the given list. */ diff --git a/libresonic-main/src/main/webapp/WEB-INF/libresonic-servlet.xml b/libresonic-main/src/main/webapp/WEB-INF/libresonic-servlet.xml index 52b0f556..edece234 100644 --- a/libresonic-main/src/main/webapp/WEB-INF/libresonic-servlet.xml +++ b/libresonic-main/src/main/webapp/WEB-INF/libresonic-servlet.xml @@ -117,7 +117,7 @@ - +