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..e741635d 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,33 @@ 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; + Integer minPlayCount = null; + Integer maxPlayCount = 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 +77,151 @@ 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 play count filter + Integer playCountValue = null; + try { playCountValue = Integer.parseInt(request.getParameter("playCountValue")); } + catch (NumberFormatException e) { } + String playCountComp = request.getParameter("playCountComp"); + if (playCountValue != null) { + switch (playCountComp) { + case "lt": + minPlayCount = null; + maxPlayCount = playCountValue - 1; + break; + case "gt": + minPlayCount = playCountValue + 1; + maxPlayCount = null; + break; + case "le": + minPlayCount = null; + maxPlayCount = playCountValue; + break; + case "ge": + minPlayCount = playCountValue; + maxPlayCount = null; + break; + case "eq": + minPlayCount = playCountValue; + maxPlayCount = playCountValue; + 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); + + // Do we add to the current playlist or do we replace it? + boolean shouldAddToPlayList = ServletRequestUtils.getBooleanParameter(request, "addToPlaylist", false); + + // Search the database using these criteria + RandomSearchCriteria criteria = new RandomSearchCriteria( + size, + genre, + fromYear, + toYear, + musicFolders, + minLastPlayedDate, + maxLastPlayedDate, + minAlbumRating, + maxAlbumRating, + minPlayCount, + maxPlayCount, + 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(shouldAddToPlayList, mediaFileService.getRandomSongs(criteria, user.getUsername())); if (request.getParameter("autoRandom") != null) { playQueue.setRandomSearchCriteria(criteria); @@ -110,10 +252,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 +259,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..3d964a4b 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,114 @@ 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("minPlayCount", criteria.getMinPlayCount()); + put("maxPlayCount", criteria.getMaxPlayCount()); + 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.getMinPlayCount() != null) { + query += " and media_file.play_count >= :minPlayCount"; + } + + if (criteria.getMaxPlayCount() != null) { + if (criteria.getMinPlayCount() == null) { + query += " and (media_file.play_count is null or media_file.play_count <= :maxPlayCount)"; + } else { + query += " and media_file.play_count <= :maxPlayCount"; + } + } + + 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/domain/RandomSearchCriteria.java b/libresonic-main/src/main/java/org/libresonic/player/domain/RandomSearchCriteria.java index a192350e..535cbe8d 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/domain/RandomSearchCriteria.java +++ b/libresonic-main/src/main/java/org/libresonic/player/domain/RandomSearchCriteria.java @@ -19,6 +19,7 @@ */ package org.libresonic.player.domain; +import java.util.Date; import java.util.List; /** @@ -34,6 +35,15 @@ public class RandomSearchCriteria { private final Integer fromYear; private final Integer toYear; private final List musicFolders; + private final Date minLastPlayedDate; + private final Date maxLastPlayedDate; + private final Integer minAlbumRating; + private final Integer maxAlbumRating; + private final Integer minPlayCount; + private final Integer maxPlayCount; + private final boolean showStarredSongs; + private final boolean showUnstarredSongs; + private final String format; /** * Creates a new instance. @@ -45,11 +55,61 @@ public class RandomSearchCriteria { * @param musicFolders Only return songs from these music folder. May NOT be null. */ public RandomSearchCriteria(int count, String genre, Integer fromYear, Integer toYear, List musicFolders) { + this( + count, genre, fromYear, toYear, musicFolders, + null, null, null, null, null, null, true, true, null + ); + } + + /** + * Creates a new instance. + * + * @param count Maximum number of songs to return. + * @param genre Only return songs of the given genre. May be null. + * @param fromYear Only return songs released after (or in) this year. May be null. + * @param toYear Only return songs released before (or in) this year. May be null. + * @param musicFolders Only return songs from these music folder. May NOT be null. + * @param minLastPlayedDate Only return songs last played after this date. May be null. + * @param maxLastPlayedDate Only return songs last played before this date. May be null. + * @param minAlbumRating Only return songs rated more or equalt to this value. May be null. + * @param maxAlbumRating Only return songs rated less or equal to this value. May be null. + * @param minPlayCount Only return songs whose play count is more or equal to this value. May be null. + * @param maxPlayCount Only return songs whose play count is less or equal to this value. May be null. + * @param showStarredSongs Show starred songs. May NOT be null. + * @param showUnstarredSongs Show unstarred songs. May NOT be null. + * @param format Only return songs whose file format is equal to this value. May be null. + */ + public RandomSearchCriteria( + int count, + String genre, + Integer fromYear, + Integer toYear, + List musicFolders, + Date minLastPlayedDate, + Date maxLastPlayedDate, + Integer minAlbumRating, + Integer maxAlbumRating, + Integer minPlayCount, + Integer maxPlayCount, + boolean showStarredSongs, + boolean showUnstarredSongs, + String format + ) { + this.count = count; this.genre = genre; this.fromYear = fromYear; this.toYear = toYear; this.musicFolders = musicFolders; + this.minLastPlayedDate = minLastPlayedDate; + this.maxLastPlayedDate = maxLastPlayedDate; + this.minAlbumRating = minAlbumRating; + this.maxAlbumRating = maxAlbumRating; + this.minPlayCount = minPlayCount; + this.maxPlayCount = maxPlayCount; + this.showStarredSongs = showStarredSongs; + this.showUnstarredSongs = showUnstarredSongs; + this.format = format; } public int getCount() { @@ -71,4 +131,40 @@ public class RandomSearchCriteria { public List getMusicFolders() { return musicFolders; } + + public Date getMinLastPlayedDate() { + return minLastPlayedDate; + } + + public Date getMaxLastPlayedDate() { + return maxLastPlayedDate; + } + + public Integer getMinAlbumRating() { + return minAlbumRating; + } + + public Integer getMaxAlbumRating() { + return maxAlbumRating; + } + + public boolean isShowStarredSongs() { + return showStarredSongs; + } + + public boolean isShowUnstarredSongs() { + return showUnstarredSongs; + } + + public String getFormat() { + return format; + } + + public Integer getMinPlayCount() { + return minPlayCount; + } + + public Integer getMaxPlayCount() { + return maxPlayCount; + } } 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/resources/org/libresonic/player/i18n/ResourceBundle_en.properties b/libresonic-main/src/main/resources/org/libresonic/player/i18n/ResourceBundle_en.properties index 5fac1bc9..771acad9 100644 --- a/libresonic-main/src/main/resources/org/libresonic/player/i18n/ResourceBundle_en.properties +++ b/libresonic-main/src/main/resources/org/libresonic/player/i18n/ResourceBundle_en.properties @@ -233,12 +233,29 @@ more.random.text = Shuffle play more.random.songs = {0} songs more.random.auto = Play more random songs when end of play queue is reached. more.random.ok = OK -more.random.genre = from genre +more.random.addtoplaylist = Add to current playlist +more.random.any = Any +more.random.format = Format +more.random.genre = Genre more.random.anygenre = Any -more.random.year = and year +more.random.year = Year more.random.anyyear = Any -more.random.folder = in folder +more.random.folder = Folder more.random.anyfolder = Any +more.random.star = star +more.random.stars = stars +more.random.starred = Starred +more.random.unstarred = Unstarred +more.random.songrating = Song rating +more.random.albumrating = Album rating +more.random.lastplayed = Last played +more.random.playcount = Play count +more.random.1day = 1 day ago +more.random.1week = 1 week ago +more.random.1month = 1 month ago +more.random.3months = 3 months ago +more.random.6months = 6 months ago +more.random.1year = 1 year ago more.apps.title = Libresonic Apps more.apps.text =

Check out the steadily growing list of Libresonic apps. \ These provide fun and alternative ways to enjoy your media collection - no matter where you are. \ diff --git a/libresonic-main/src/main/webapp/WEB-INF/jsp/more.jsp b/libresonic-main/src/main/webapp/WEB-INF/jsp/more.jsp index 4993717f..3d708bf5 100644 --- a/libresonic-main/src/main/webapp/WEB-INF/jsp/more.jsp +++ b/libresonic-main/src/main/webapp/WEB-INF/jsp/more.jsp @@ -2,9 +2,11 @@ <%@ include file="head.jsp" %> + <%@ include file="jquery.jsp" %> @@ -36,6 +38,58 @@ window.setTimeout("refreshProgress()", 5000); } } + + // From Modernizr + // See: https://modernizr.com/ + function isLocalStorageEnabled() { + var mod = 'modernizr'; + try { + localStorage.setItem(mod, mod); + localStorage.removeItem(mod); + return true; + } catch(e) { + return false; + } + } + + + // Load previously used shuffle parameters + function loadShuffleForm() { + if (!isLocalStorageEnabled()) return; + var form = document.getElementById("randomPlayQueue"); + try { + var data = JSON.parse(localStorage.getItem("randomPlayQueue")); + } catch(e) { return; } + elements = form.getElementsByTagName("input"); + for (var i = 0; i < elements.length; i++) { + if (elements[i].type == "submit") continue; + if (data[elements[i].name]) elements[i].value = data[elements[i].name]; + } + elements = form.getElementsByTagName("select"); + for (var i = 0; i < elements.length; i++) { + if (data[elements[i].name]) elements[i].value = data[elements[i].name]; + } + } + + // Save shuffle parameters + function saveShuffleForm() { + if (!isLocalStorageEnabled()) return; + var form = document.getElementById("randomPlayQueue"); + var data = {} + var elements = []; + elements = form.getElementsByTagName("input"); + for (var i = 0; i < elements.length; i++) data[elements[i].name] = elements[i].value; + elements = form.getElementsByTagName("select"); + for (var i = 0; i < elements.length; i++) data[elements[i].name] = elements[i].value; + localStorage.setItem("randomPlayQueue", JSON.stringify(data)); + } + + $(function() { + ${model.user.uploadRole ? "refreshProgress();" : ""} + $("#randomPlayQueue").on("submit", saveShuffleForm); + loadShuffleForm(); + }); + - +

" alt=""/> @@ -58,31 +112,23 @@

-
+ - - + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ - +
- - - - - @@ -101,17 +147,99 @@
+ + + + +
+ + + + +
- + + times +
+ + - "> + +
+ ">