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.
master
François-Xavier Thomas 8 years ago
parent 8ff762ed0f
commit 71b17a8588
No known key found for this signature in database
GPG Key ID: 64337406D2DD45CE
  1. 157
      libresonic-main/src/main/java/org/libresonic/player/controller/RandomPlayQueueController.java
  2. 98
      libresonic-main/src/main/java/org/libresonic/player/dao/MediaFileDao.java
  3. 20
      libresonic-main/src/main/java/org/libresonic/player/service/MediaFileService.java
  4. 2
      libresonic-main/src/main/webapp/WEB-INF/libresonic-servlet.xml

@ -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<ReloadFrame> 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<MusicFolder> 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;
}
}

@ -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<MediaFile> getRandomSongs(RandomSearchCriteria criteria, final String username) {
if (criteria.getMusicFolders().isEmpty()) {
return Collections.emptyList();
}
Map<String, Object> args = new HashMap<String, Object>() {{
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<MusicFolder> musicFolders) {
if (musicFolders.isEmpty()) {
return 0;

@ -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<MediaFile> getRandomSongs(RandomSearchCriteria criteria, String username) {
return mediaFileDao.getRandomSongs(criteria, username);
}
/**
* Removes video files from the given list.
*/

@ -117,7 +117,7 @@
<bean id="randomPlayQueueController" class="org.libresonic.player.controller.RandomPlayQueueController">
<property name="viewName" value="reload"/>
<property name="playerService" ref="playerService"/>
<property name="searchService" ref="searchService"/>
<property name="mediaFileService" ref="mediaFileService"/>
<property name="securityService" ref="securityService"/>
<property name="settingsService" ref="settingsService"/>
<property name="reloadFrames">

Loading…
Cancel
Save