diff --git a/BUILD.md b/documentation/BUILD.md similarity index 100% rename from BUILD.md rename to documentation/BUILD.md diff --git a/MIGRATE.md b/documentation/MIGRATE.md similarity index 100% rename from MIGRATE.md rename to documentation/MIGRATE.md diff --git a/documentation/PROXY.md b/documentation/PROXY.md new file mode 100644 index 00000000..b922f6a5 --- /dev/null +++ b/documentation/PROXY.md @@ -0,0 +1,109 @@ +# Setting up a reverse proxy + +A reverse proxy is a public-facing web server sitting in front of an internal +server such as Libresonic. The Libresonic server never communicates with the +outside ; instead, the reverse proxy handles all HTTP(S) requests and forwards +them to Libresonic. + +This is useful in many ways, such as gathering all web configuration in the +same place. It also handles some options (HTTPS) much better than the bundled +Libresonic server or a servlet container such as Tomcat. + +This guide assumes you already have a working Libresonic installation after +following the [installation guide](documentation/INSTALL.md). + +## Getting a TLS certificate + +This guide assumes you already have a TLS certificate. [Let's +Encrypt](https://letsencrypt.org) currently provides such certificates for +free. + +## Libresonic configuration + +A few settings can be tweaked in Libresonic's startup script or Tomcat +configuration. + +The reverse proxy will handle HTTPS connections, so there is no need for +Libresonic to handle them, which is why we set `httpsPort` to 0: + + libresonic.httpsPort=0 + +Furthermore, the internal Libresonic server should only be accessible from the +inside of the reverse proxy : we tell Libresonic to listen on the local IP +only: + + libresonic.host=127.0.0.1 + libresonic.port=4040 + +Finally, if Libresonic should be accessible from a subdirectory, the context +path must be set correctly: + + libresonic.contextPath=/libresonic + +## Reverse proxy configuration + +### Nginx + +The following configuration works for Nginx (HTTPS with HTTP redirection): + +```nginx +# Redirect HTTP to HTTPS +server { + listen 80; + server_name example.com; + return 301 https://$server_name$request_uri; +} + +server { + + # Setup HTTPS certificates + listen 443 default ssl; + server_name example.com; + ssl_certificate cert.pem; + ssl_certificate_key key.pem; + + # Proxy to the Libresonic server + location /libresonic { + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header Host $http_host; + proxy_max_temp_file_size 0; + proxy_pass http://127.0.0.1:4040; + proxy_redirect http:// https://; + } +} +``` + +### Apache + +The following configuration works for Apache (without HTTPS): + +```apache + + ServerName example.com + ErrorDocument 404 /404.html + DocumentRoot /var/www + ProxyPass /libresonic http://localhost:4040/libresonic + ProxyPassReverse /libresonic http://localhost:4040/libresonic + +``` + +### HAProxy + +The following configuration works for HAProxy (HTTPS only): + +```haproxy +frontend https + bind $server_public_ip$:443 ssl crt /etc/haproxy/ssl/$server_ssl_keys$.pem + + # Let Libresonic handle all requests under /libresonic + acl url_libresonic path_beg -i /libresonic + use_backend libresonic-backend if url_libresonic + + # Change default backend to libresonic backend if you don't have a web backend + default_backend web-backend + +backend libresonic-backend + server libresonic 127.0.0.1:4040 check +``` diff --git a/libresonic-assembly/pom.xml b/libresonic-assembly/pom.xml index 97ba9468..d77e8e89 100644 --- a/libresonic-assembly/pom.xml +++ b/libresonic-assembly/pom.xml @@ -9,7 +9,7 @@ org.libresonic.player libresonic - 6.2.spring4-SNAPSHOT + 6.2.beta1.spring4 diff --git a/libresonic-booter/pom.xml b/libresonic-booter/pom.xml index 8c19afde..8831c500 100644 --- a/libresonic-booter/pom.xml +++ b/libresonic-booter/pom.xml @@ -8,7 +8,7 @@ org.libresonic.player libresonic - 6.2.spring4-SNAPSHOT + 6.2.beta1.spring4 diff --git a/libresonic-installer-debian/pom.xml b/libresonic-installer-debian/pom.xml index 915e2ca5..39baa5f9 100644 --- a/libresonic-installer-debian/pom.xml +++ b/libresonic-installer-debian/pom.xml @@ -9,7 +9,7 @@ org.libresonic.player libresonic - 6.2.spring4-SNAPSHOT + 6.2.beta1.spring4 diff --git a/libresonic-installer-mac/pom.xml b/libresonic-installer-mac/pom.xml index 7095aa86..a20abb07 100644 --- a/libresonic-installer-mac/pom.xml +++ b/libresonic-installer-mac/pom.xml @@ -9,7 +9,7 @@ org.libresonic.player libresonic - 6.2.spring4-SNAPSHOT + 6.2.beta1.spring4 diff --git a/libresonic-installer-rpm/pom.xml b/libresonic-installer-rpm/pom.xml index ef68ab1d..794d29b3 100644 --- a/libresonic-installer-rpm/pom.xml +++ b/libresonic-installer-rpm/pom.xml @@ -9,7 +9,7 @@ org.libresonic.player libresonic - 6.2.spring4-SNAPSHOT + 6.2.beta1.spring4 diff --git a/libresonic-installer-windows/pom.xml b/libresonic-installer-windows/pom.xml index a49724f1..b213a361 100644 --- a/libresonic-installer-windows/pom.xml +++ b/libresonic-installer-windows/pom.xml @@ -9,7 +9,7 @@ org.libresonic.player libresonic - 6.2.spring4-SNAPSHOT + 6.2.beta1.spring4 diff --git a/libresonic-main/pom.xml b/libresonic-main/pom.xml index df895f37..9a28397e 100644 --- a/libresonic-main/pom.xml +++ b/libresonic-main/pom.xml @@ -9,7 +9,7 @@ org.libresonic.player libresonic - 6.2.spring4-SNAPSHOT + 6.2.beta1.spring4 @@ -136,13 +136,13 @@ org.apache.httpcomponents httpcore - 4.2.4 + 4.4.5 org.apache.httpcomponents httpclient - 4.2.4 + 4.5.2 diff --git a/libresonic-main/src/main/java/org/libresonic/player/ajax/CoverArtService.java b/libresonic-main/src/main/java/org/libresonic/player/ajax/CoverArtService.java index 9464ecdc..360c668d 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/ajax/CoverArtService.java +++ b/libresonic-main/src/main/java/org/libresonic/player/ajax/CoverArtService.java @@ -25,11 +25,11 @@ import java.io.InputStream; import java.io.OutputStream; import org.apache.commons.io.IOUtils; -import org.apache.http.HttpResponse; -import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.params.HttpConnectionParams; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; import org.libresonic.player.Logger; import org.libresonic.player.domain.MediaFile; @@ -72,69 +72,69 @@ public class CoverArtService { private void saveCoverArt(String path, String url) throws Exception { InputStream input = null; OutputStream output = null; - HttpClient client = new DefaultHttpClient(); - try { - HttpConnectionParams.setConnectionTimeout(client.getParams(), 20 * 1000); // 20 seconds - HttpConnectionParams.setSoTimeout(client.getParams(), 20 * 1000); // 20 seconds + try (CloseableHttpClient client = HttpClients.createDefault()) { + RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(20 * 1000) // 20 seconds + .setSocketTimeout(20 * 1000) // 20 seconds + .build(); HttpGet method = new HttpGet(url); + method.setConfig(requestConfig); + try (CloseableHttpResponse response = client.execute(method)) { + input = response.getEntity().getContent(); + + // Attempt to resolve proper suffix. + String suffix = "jpg"; + if (url.toLowerCase().endsWith(".gif")) { + suffix = "gif"; + } else if (url.toLowerCase().endsWith(".png")) { + suffix = "png"; + } - HttpResponse response = client.execute(method); - input = response.getEntity().getContent(); - - // Attempt to resolve proper suffix. - String suffix = "jpg"; - if (url.toLowerCase().endsWith(".gif")) { - suffix = "gif"; - } else if (url.toLowerCase().endsWith(".png")) { - suffix = "png"; - } - - // Check permissions. - File newCoverFile = new File(path, "cover." + suffix); - if (!securityService.isWriteAllowed(newCoverFile)) { - throw new Exception("Permission denied: " + StringUtil.toHtml(newCoverFile.getPath())); - } - - // If file exists, create a backup. - backup(newCoverFile, new File(path, "cover." + suffix + ".backup")); - - // Write file. - output = new FileOutputStream(newCoverFile); - IOUtils.copy(input, output); - - MediaFile dir = mediaFileService.getMediaFile(path); - - // Refresh database. - mediaFileService.refreshMediaFile(dir); - dir = mediaFileService.getMediaFile(dir.getId()); + // Check permissions. + File newCoverFile = new File(path, "cover." + suffix); + if (!securityService.isWriteAllowed(newCoverFile)) { + throw new Exception("Permission denied: " + StringUtil.toHtml(newCoverFile.getPath())); + } - // Rename existing cover files if new cover file is not the preferred. - try { - while (true) { - File coverFile = mediaFileService.getCoverArt(dir); - if (coverFile != null && !isMediaFile(coverFile) && !newCoverFile.equals(coverFile)) { - if (!coverFile.renameTo(new File(coverFile.getCanonicalPath() + ".old"))) { - LOG.warn("Unable to rename old image file " + coverFile); + // If file exists, create a backup. + backup(newCoverFile, new File(path, "cover." + suffix + ".backup")); + + // Write file. + output = new FileOutputStream(newCoverFile); + IOUtils.copy(input, output); + + MediaFile dir = mediaFileService.getMediaFile(path); + + // Refresh database. + mediaFileService.refreshMediaFile(dir); + dir = mediaFileService.getMediaFile(dir.getId()); + + // Rename existing cover files if new cover file is not the preferred. + try { + while (true) { + File coverFile = mediaFileService.getCoverArt(dir); + if (coverFile != null && !isMediaFile(coverFile) && !newCoverFile.equals(coverFile)) { + if (!coverFile.renameTo(new File(coverFile.getCanonicalPath() + ".old"))) { + LOG.warn("Unable to rename old image file " + coverFile); + break; + } + LOG.info("Renamed old image file " + coverFile); + + // Must refresh again. + mediaFileService.refreshMediaFile(dir); + dir = mediaFileService.getMediaFile(dir.getId()); + } else { break; } - LOG.info("Renamed old image file " + coverFile); - - // Must refresh again. - mediaFileService.refreshMediaFile(dir); - dir = mediaFileService.getMediaFile(dir.getId()); - } else { - break; } + } catch (Exception x) { + LOG.warn("Failed to rename existing cover file.", x); } - } catch (Exception x) { - LOG.warn("Failed to rename existing cover file.", x); } - } finally { IOUtils.closeQuietly(input); IOUtils.closeQuietly(output); - client.getConnectionManager().shutdown(); } } diff --git a/libresonic-main/src/main/java/org/libresonic/player/ajax/LyricsService.java b/libresonic-main/src/main/java/org/libresonic/player/ajax/LyricsService.java index 9ccc46a6..f2578614 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/ajax/LyricsService.java +++ b/libresonic-main/src/main/java/org/libresonic/player/ajax/LyricsService.java @@ -24,12 +24,12 @@ import java.io.StringReader; import java.net.SocketException; import org.apache.commons.lang.StringUtils; -import org.apache.http.client.HttpClient; import org.apache.http.client.ResponseHandler; +import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.BasicResponseHandler; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.params.HttpConnectionParams; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; import org.jdom.Document; import org.jdom.Element; import org.jdom.Namespace; @@ -92,17 +92,15 @@ public class LyricsService { } private String executeGetRequest(String url) throws IOException { - HttpClient client = new DefaultHttpClient(); - HttpConnectionParams.setConnectionTimeout(client.getParams(), 15000); - HttpConnectionParams.setSoTimeout(client.getParams(), 15000); + RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(15000) + .setSocketTimeout(15000) + .build(); HttpGet method = new HttpGet(url); - try { - + method.setConfig(requestConfig); + try (CloseableHttpClient client = HttpClients.createDefault()) { ResponseHandler responseHandler = new BasicResponseHandler(); return client.execute(method, responseHandler); - - } finally { - client.getConnectionManager().shutdown(); } } } diff --git a/libresonic-main/src/main/java/org/libresonic/player/ajax/PlayQueueInfo.java b/libresonic-main/src/main/java/org/libresonic/player/ajax/PlayQueueInfo.java index 8d522cc4..0cb5f541 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/ajax/PlayQueueInfo.java +++ b/libresonic-main/src/main/java/org/libresonic/player/ajax/PlayQueueInfo.java @@ -33,15 +33,17 @@ public class PlayQueueInfo { private final List entries; private final boolean stopEnabled; private final boolean repeatEnabled; + private final boolean radioEnabled; private final boolean sendM3U; private final float gain; private int startPlayerAt = -1; private long startPlayerAtPosition; // millis - public PlayQueueInfo(List entries, boolean stopEnabled, boolean repeatEnabled, boolean sendM3U, float gain) { + public PlayQueueInfo(List entries, boolean stopEnabled, boolean repeatEnabled, boolean radioEnabled, boolean sendM3U, float gain) { this.entries = entries; this.stopEnabled = stopEnabled; this.repeatEnabled = repeatEnabled; + this.radioEnabled = radioEnabled; this.sendM3U = sendM3U; this.gain = gain; } @@ -72,6 +74,10 @@ public class PlayQueueInfo { return repeatEnabled; } + public boolean isRadioEnabled() { + return radioEnabled; + } + public float getGain() { return gain; } @@ -215,4 +221,4 @@ public class PlayQueueInfo { return remoteCoverArtUrl; } } -} \ No newline at end of file +} diff --git a/libresonic-main/src/main/java/org/libresonic/player/ajax/PlayQueueService.java b/libresonic-main/src/main/java/org/libresonic/player/ajax/PlayQueueService.java index a2496290..bf8ff160 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/ajax/PlayQueueService.java +++ b/libresonic-main/src/main/java/org/libresonic/player/ajax/PlayQueueService.java @@ -148,6 +148,18 @@ public class PlayQueueService { return convert(request, player, serverSidePlaylist, offset); } + public PlayQueueInfo reloadSearchCriteria() throws Exception { + HttpServletRequest request = WebContextFactory.get().getHttpServletRequest(); + HttpServletResponse response = WebContextFactory.get().getHttpServletResponse(); + String username = securityService.getCurrentUsername(request); + Player player = getCurrentPlayer(request, response); + PlayQueue playQueue = player.getPlayQueue(); + if (playQueue.getRandomSearchCriteria() != null) { + playQueue.addFiles(true, mediaFileService.getRandomSongs(playQueue.getRandomSearchCriteria(), username)); + } + return convert(request, player, false); + } + public void savePlayQueue(int currentSongIndex, long positionMillis) { HttpServletRequest request = WebContextFactory.get().getHttpServletRequest(); HttpServletResponse response = WebContextFactory.get().getHttpServletResponse(); @@ -581,7 +593,13 @@ public class PlayQueueService { HttpServletRequest request = WebContextFactory.get().getHttpServletRequest(); HttpServletResponse response = WebContextFactory.get().getHttpServletResponse(); Player player = getCurrentPlayer(request, response); - player.getPlayQueue().setRepeatEnabled(!player.getPlayQueue().isRepeatEnabled()); + PlayQueue playQueue = player.getPlayQueue(); + if (playQueue.isRadioEnabled()) { + playQueue.setRandomSearchCriteria(null); + playQueue.setRepeatEnabled(false); + } else { + playQueue.setRepeatEnabled(!player.getPlayQueue().isRepeatEnabled()); + } return convert(request, player, false); } @@ -668,7 +686,7 @@ public class PlayQueueService { } boolean isStopEnabled = playQueue.getStatus() == PlayQueue.Status.PLAYING && !player.isExternalWithPlaylist(); float gain = jukeboxService.getGain(); - return new PlayQueueInfo(entries, isStopEnabled, playQueue.isRepeatEnabled(), serverSidePlaylist, gain); + return new PlayQueueInfo(entries, isStopEnabled, playQueue.isRepeatEnabled(), playQueue.isRadioEnabled(), serverSidePlaylist, gain); } private String formatFileSize(Long fileSize, Locale locale) { @@ -751,4 +769,4 @@ public class PlayQueueService { public void setPlaylistService(PlaylistService playlistService) { this.playlistService = playlistService; } -} \ No newline at end of file +} diff --git a/libresonic-main/src/main/java/org/libresonic/player/controller/ProxyController.java b/libresonic-main/src/main/java/org/libresonic/player/controller/ProxyController.java index 06691f1e..552aae50 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/controller/ProxyController.java +++ b/libresonic-main/src/main/java/org/libresonic/player/controller/ProxyController.java @@ -25,12 +25,12 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.IOUtils; -import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; -import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.params.HttpConnectionParams; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; import org.springframework.web.bind.ServletRequestUtils; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller; @@ -45,24 +45,26 @@ public class ProxyController implements Controller { public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { String url = ServletRequestUtils.getRequiredStringParameter(request, "url"); - HttpClient client = new DefaultHttpClient(); - HttpConnectionParams.setConnectionTimeout(client.getParams(), 15000); - HttpConnectionParams.setSoTimeout(client.getParams(), 15000); + RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(15000) + .setSocketTimeout(15000) + .build(); HttpGet method = new HttpGet(url); + method.setConfig(requestConfig); InputStream in = null; - try { - HttpResponse resp = client.execute(method); - int statusCode = resp.getStatusLine().getStatusCode(); - if (statusCode != HttpStatus.SC_OK) { - response.sendError(statusCode); - } else { - in = resp.getEntity().getContent(); - IOUtils.copy(in, response.getOutputStream()); + try (CloseableHttpClient client = HttpClients.createDefault()) { + try (CloseableHttpResponse resp = client.execute(method)) { + int statusCode = resp.getStatusLine().getStatusCode(); + if (statusCode != HttpStatus.SC_OK) { + response.sendError(statusCode); + } else { + in = resp.getEntity().getContent(); + IOUtils.copy(in, response.getOutputStream()); + } } } finally { IOUtils.closeQuietly(in); - client.getConnectionManager().shutdown(); } return null; } 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 e741635d..886f278c 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 @@ -199,7 +199,7 @@ public class RandomPlayQueueController extends ParameterizableViewController { List musicFolders = getMusicFolders(request); // Do we add to the current playlist or do we replace it? - boolean shouldAddToPlayList = ServletRequestUtils.getBooleanParameter(request, "addToPlaylist", false); + boolean shouldAddToPlayList = request.getParameter("addToPlaylist") != null; // Search the database using these criteria RandomSearchCriteria criteria = new RandomSearchCriteria( diff --git a/libresonic-main/src/main/java/org/libresonic/player/dao/schema/hsql/Schema62.java b/libresonic-main/src/main/java/org/libresonic/player/dao/schema/hsql/Schema62.java index a8a5fb0c..cdd233c2 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/dao/schema/hsql/Schema62.java +++ b/libresonic-main/src/main/java/org/libresonic/player/dao/schema/hsql/Schema62.java @@ -39,7 +39,7 @@ public class Schema62 extends Schema { template.execute("insert into version values (27)"); } - if (!columnExists(template, "player", "m3u_bom_enabled")) { + if (!columnExists(template, "m3u_bom_enabled", "player")) { LOG.info("Database column 'player.m3u_bom_enabled' not found. Creating it."); template.execute("alter table player add m3u_bom_enabled boolean default false not null"); LOG.info("Database column 'player.m3u_bom_enabled' was added successfully."); diff --git a/libresonic-main/src/main/java/org/libresonic/player/domain/PlayQueue.java b/libresonic-main/src/main/java/org/libresonic/player/domain/PlayQueue.java index 2cc2ae42..ef665d19 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/domain/PlayQueue.java +++ b/libresonic-main/src/main/java/org/libresonic/player/domain/PlayQueue.java @@ -368,6 +368,15 @@ public class PlayQueue { this.repeatEnabled = repeatEnabled; } + /** + * Returns whether the playlist is a shuffle radio + * + * @return Whether the playlist is a shuffle radio. + */ + public synchronized boolean isRadioEnabled() { + return this.randomSearchCriteria != null; + } + /** * Revert the last operation. */ @@ -455,4 +464,4 @@ public class PlayQueue { ARTIST, ALBUM } -} \ No newline at end of file +} diff --git a/libresonic-main/src/main/java/org/libresonic/player/service/AudioScrobblerService.java b/libresonic-main/src/main/java/org/libresonic/player/service/AudioScrobblerService.java index fcdc76fc..9546ab30 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/service/AudioScrobblerService.java +++ b/libresonic-main/src/main/java/org/libresonic/player/service/AudioScrobblerService.java @@ -29,16 +29,16 @@ import java.util.concurrent.LinkedBlockingQueue; import org.apache.commons.codec.digest.DigestUtils; import org.apache.http.NameValuePair; -import org.apache.http.client.HttpClient; import org.apache.http.client.ResponseHandler; +import org.apache.http.client.config.RequestConfig; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.impl.client.BasicResponseHandler; -import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; -import org.apache.http.params.HttpConnectionParams; import org.libresonic.player.Logger; import org.libresonic.player.domain.MediaFile; @@ -62,7 +62,10 @@ public class AudioScrobblerService { private final LinkedBlockingQueue queue = new LinkedBlockingQueue(); private SettingsService settingsService; - + private final RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(15000) + .setSocketTimeout(15000) + .build(); /** * Registers the given media file at www.last.fm. This method returns immediately, the actual registration is done @@ -234,7 +237,9 @@ public class AudioScrobblerService { } private String[] executeGetRequest(String url) throws IOException { - return executeRequest(new HttpGet(url)); + HttpGet method = new HttpGet(url); + method.setConfig(requestConfig); + return executeRequest(method); } private String[] executePostRequest(String url, Map parameters) throws IOException { @@ -245,22 +250,17 @@ public class AudioScrobblerService { HttpPost request = new HttpPost(url); request.setEntity(new UrlEncodedFormEntity(params, StringUtil.ENCODING_UTF8)); - + request.setConfig(requestConfig); return executeRequest(request); } private String[] executeRequest(HttpUriRequest request) throws IOException { - HttpClient client = new DefaultHttpClient(); - HttpConnectionParams.setConnectionTimeout(client.getParams(), 15000); - HttpConnectionParams.setSoTimeout(client.getParams(), 15000); - try { + try (CloseableHttpClient client = HttpClients.createDefault()) { ResponseHandler responseHandler = new BasicResponseHandler(); String response = client.execute(request, responseHandler); return response.split("\\n"); - } finally { - client.getConnectionManager().shutdown(); } } diff --git a/libresonic-main/src/main/java/org/libresonic/player/service/NetworkService.java b/libresonic-main/src/main/java/org/libresonic/player/service/NetworkService.java index 279923c3..1992c0ea 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/service/NetworkService.java +++ b/libresonic-main/src/main/java/org/libresonic/player/service/NetworkService.java @@ -28,19 +28,18 @@ import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import org.apache.commons.lang.StringUtils; -import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.NameValuePair; import org.apache.http.StatusLine; -import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.BasicResponseHandler; -import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; -import org.apache.http.params.HttpConnectionParams; import org.apache.http.util.EntityUtils; import org.libresonic.player.Logger; @@ -250,34 +249,32 @@ public class NetworkService { params.add(new BasicNameValuePair("licenseHolder", settingsService.getLicenseEmail())); } - HttpClient client = new DefaultHttpClient(); - try { + + try (CloseableHttpClient client = HttpClients.createDefault()) { urlRedirectionStatus.setText(enable ? "Registering web address..." : "Unregistering web address..."); request.setEntity(new UrlEncodedFormEntity(params, StringUtil.ENCODING_UTF8)); - HttpResponse response = client.execute(request); - StatusLine status = response.getStatusLine(); - - switch (status.getStatusCode()) { - case HttpStatus.SC_BAD_REQUEST: - urlRedirectionStatus.setText(EntityUtils.toString(response.getEntity())); - testUrlRedirection = false; - break; - case HttpStatus.SC_OK: - urlRedirectionStatus.setText(enable ? "Successfully registered web address." : "Web address disabled."); - break; - default: - testUrlRedirection = false; - throw new IOException(status.getStatusCode() + " " + status.getReasonPhrase()); + try (CloseableHttpResponse response = client.execute(request)) { + StatusLine status = response.getStatusLine(); + + switch (status.getStatusCode()) { + case HttpStatus.SC_BAD_REQUEST: + urlRedirectionStatus.setText(EntityUtils.toString(response.getEntity())); + testUrlRedirection = false; + break; + case HttpStatus.SC_OK: + urlRedirectionStatus.setText(enable ? "Successfully registered web address." : "Web address disabled."); + break; + default: + testUrlRedirection = false; + throw new IOException(status.getStatusCode() + " " + status.getReasonPhrase()); + } } - } catch (Throwable x) { LOG.warn(enable ? "Failed to register web address." : "Failed to unregister web address.", x); urlRedirectionStatus.setText(enable ? ("Failed to register web address. " + x.getMessage() + " (" + x.getClass().getSimpleName() + ")") : "Web address disabled."); - } finally { - client.getConnectionManager().shutdown(); } // Test redirection, but only once. @@ -305,20 +302,19 @@ public class NetworkService { } HttpGet request = new HttpGet(url); - HttpClient client = new DefaultHttpClient(); - HttpConnectionParams.setConnectionTimeout(client.getParams(), 10000); - HttpConnectionParams.setSoTimeout(client.getParams(), 30000); - try { + RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(15000) + .setSocketTimeout(15000) + .build(); + request.setConfig(requestConfig); + try (CloseableHttpClient client = HttpClients.createDefault()) { urlRedirectionStatus.setText("Testing web address " + urlToTest + ". Please wait..."); String response = client.execute(request, new BasicResponseHandler()); urlRedirectionStatus.setText(response); - } catch (Throwable x) { LOG.warn("Failed to test web address.", x); urlRedirectionStatus.setText("Failed to test web address. " + x.getMessage() + " (" + x.getClass().getSimpleName() + ")"); - } finally { - client.getConnectionManager().shutdown(); } } } diff --git a/libresonic-main/src/main/java/org/libresonic/player/service/PodcastService.java b/libresonic-main/src/main/java/org/libresonic/player/service/PodcastService.java index 8754345c..7fd2ab9d 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/service/PodcastService.java +++ b/libresonic-main/src/main/java/org/libresonic/player/service/PodcastService.java @@ -39,25 +39,25 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.http.Header; import org.apache.http.HttpResponse; -import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.entity.ContentType; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.params.HttpConnectionParams; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; import org.jdom.Document; import org.jdom.Element; import org.jdom.Namespace; import org.jdom.input.SAXBuilder; -import com.google.common.base.Predicate; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; - import org.libresonic.player.Logger; import org.libresonic.player.dao.PodcastDao; import org.libresonic.player.domain.MediaFile; @@ -302,33 +302,34 @@ public class PodcastService { @SuppressWarnings({"unchecked"}) private void doRefreshChannel(PodcastChannel channel, boolean downloadEpisodes) { InputStream in = null; - HttpClient client = new DefaultHttpClient(); - try { + try (CloseableHttpClient client = HttpClients.createDefault()) { channel.setStatus(PodcastStatus.DOWNLOADING); channel.setErrorMessage(null); podcastDao.updateChannel(channel); - - HttpConnectionParams.setConnectionTimeout(client.getParams(), 2 * 60 * 1000); // 2 minutes - HttpConnectionParams.setSoTimeout(client.getParams(), 10 * 60 * 1000); // 10 minutes + RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(2 * 60 * 1000) // 2 minutes + .setSocketTimeout(10 * 60 * 1000) // 10 minutes + .build(); HttpGet method = new HttpGet(channel.getUrl()); + method.setConfig(requestConfig); - HttpResponse response = client.execute(method); - in = response.getEntity().getContent(); - - Document document = new SAXBuilder().build(in); - Element channelElement = document.getRootElement().getChild("channel"); + try (CloseableHttpResponse response = client.execute(method)) { + in = response.getEntity().getContent(); - channel.setTitle(StringUtil.removeMarkup(channelElement.getChildTextTrim("title"))); - channel.setDescription(StringUtil.removeMarkup(channelElement.getChildTextTrim("description"))); - channel.setImageUrl(getChannelImageUrl(channelElement)); - channel.setStatus(PodcastStatus.COMPLETED); - channel.setErrorMessage(null); - podcastDao.updateChannel(channel); + Document document = new SAXBuilder().build(in); + Element channelElement = document.getRootElement().getChild("channel"); - downloadImage(channel); - refreshEpisodes(channel, channelElement.getChildren("item")); + channel.setTitle(StringUtil.removeMarkup(channelElement.getChildTextTrim("title"))); + channel.setDescription(StringUtil.removeMarkup(channelElement.getChildTextTrim("description"))); + channel.setImageUrl(getChannelImageUrl(channelElement)); + channel.setStatus(PodcastStatus.COMPLETED); + channel.setErrorMessage(null); + podcastDao.updateChannel(channel); + downloadImage(channel); + refreshEpisodes(channel, channelElement.getChildren("item")); + } } catch (Exception x) { LOG.warn("Failed to get/parse RSS file for Podcast channel " + channel.getUrl(), x); channel.setStatus(PodcastStatus.ERROR); @@ -336,7 +337,6 @@ public class PodcastService { podcastDao.updateChannel(channel); } finally { IOUtils.closeQuietly(in); - client.getConnectionManager().shutdown(); } if (downloadEpisodes) { @@ -349,10 +349,9 @@ public class PodcastService { } private void downloadImage(PodcastChannel channel) { - HttpClient client = new DefaultHttpClient(); InputStream in = null; OutputStream out = null; - try { + try(CloseableHttpClient client = HttpClients.createDefault()) { String imageUrl = channel.getImageUrl(); if (imageUrl == null) { return; @@ -367,17 +366,17 @@ public class PodcastService { } HttpGet method = new HttpGet(imageUrl); - HttpResponse response = client.execute(method); - in = response.getEntity().getContent(); - out = new FileOutputStream(new File(dir, "cover." + getCoverArtSuffix(response))); - IOUtils.copy(in, out); - mediaFileService.refreshMediaFile(channelMediaFile); + try (CloseableHttpResponse response = client.execute(method)) { + in = response.getEntity().getContent(); + out = new FileOutputStream(new File(dir, "cover." + getCoverArtSuffix(response))); + IOUtils.copy(in, out); + mediaFileService.refreshMediaFile(channelMediaFile); + } } catch (Exception x) { LOG.warn("Failed to download cover art for podcast channel '" + channel.getTitle() + "': " + x, x); } finally { IOUtils.closeQuietly(in); IOUtils.closeQuietly(out); - client.getConnectionManager().shutdown(); } } @@ -534,68 +533,69 @@ public class PodcastService { LOG.info("Starting to download Podcast from " + episode.getUrl()); - HttpClient client = new DefaultHttpClient(); - try { + try (CloseableHttpClient client = HttpClients.createDefault()) { if (!settingsService.getLicenseInfo().isLicenseOrTrialValid()) { throw new Exception("Sorry, the trial period is expired."); } PodcastChannel channel = getChannel(episode.getChannelId()); - - HttpConnectionParams.setConnectionTimeout(client.getParams(), 2 * 60 * 1000); // 2 minutes - HttpConnectionParams.setSoTimeout(client.getParams(), 10 * 60 * 1000); // 10 minutes + RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(2 * 60 * 1000) // 2 minutes + .setSocketTimeout(10 * 60 * 1000) // 10 minutes + .build(); HttpGet method = new HttpGet(episode.getUrl()); + method.setConfig(requestConfig); - HttpResponse response = client.execute(method); - in = response.getEntity().getContent(); + try (CloseableHttpResponse response = client.execute(method)) { + in = response.getEntity().getContent(); - File file = getFile(channel, episode); - out = new FileOutputStream(file); + File file = getFile(channel, episode); + out = new FileOutputStream(file); - episode.setStatus(PodcastStatus.DOWNLOADING); - episode.setBytesDownloaded(0L); - episode.setErrorMessage(null); - episode.setPath(file.getPath()); - podcastDao.updateEpisode(episode); + episode.setStatus(PodcastStatus.DOWNLOADING); + episode.setBytesDownloaded(0L); + episode.setErrorMessage(null); + episode.setPath(file.getPath()); + podcastDao.updateEpisode(episode); - byte[] buffer = new byte[4096]; - long bytesDownloaded = 0; - int n; - long nextLogCount = 30000L; + byte[] buffer = new byte[4096]; + long bytesDownloaded = 0; + int n; + long nextLogCount = 30000L; - while ((n = in.read(buffer)) != -1) { - out.write(buffer, 0, n); - bytesDownloaded += n; + while ((n = in.read(buffer)) != -1) { + out.write(buffer, 0, n); + bytesDownloaded += n; - if (bytesDownloaded > nextLogCount) { - episode.setBytesDownloaded(bytesDownloaded); - nextLogCount += 30000L; + if (bytesDownloaded > nextLogCount) { + episode.setBytesDownloaded(bytesDownloaded); + nextLogCount += 30000L; - // Abort download if episode was deleted by user. - if (isEpisodeDeleted(episode)) { - break; + // Abort download if episode was deleted by user. + if (isEpisodeDeleted(episode)) { + break; + } + podcastDao.updateEpisode(episode); } - podcastDao.updateEpisode(episode); } - } - if (isEpisodeDeleted(episode)) { - LOG.info("Podcast " + episode.getUrl() + " was deleted. Aborting download."); - IOUtils.closeQuietly(out); - file.delete(); - } else { - addMediaFileIdToEpisodes(Arrays.asList(episode)); - episode.setBytesDownloaded(bytesDownloaded); - podcastDao.updateEpisode(episode); - LOG.info("Downloaded " + bytesDownloaded + " bytes from Podcast " + episode.getUrl()); - IOUtils.closeQuietly(out); - updateTags(file, episode); - episode.setStatus(PodcastStatus.COMPLETED); - podcastDao.updateEpisode(episode); - deleteObsoleteEpisodes(channel); + if (isEpisodeDeleted(episode)) { + LOG.info("Podcast " + episode.getUrl() + " was deleted. Aborting download."); + IOUtils.closeQuietly(out); + file.delete(); + } else { + addMediaFileIdToEpisodes(Arrays.asList(episode)); + episode.setBytesDownloaded(bytesDownloaded); + podcastDao.updateEpisode(episode); + LOG.info("Downloaded " + bytesDownloaded + " bytes from Podcast " + episode.getUrl()); + IOUtils.closeQuietly(out); + updateTags(file, episode); + episode.setStatus(PodcastStatus.COMPLETED); + podcastDao.updateEpisode(episode); + deleteObsoleteEpisodes(channel); + } } - } catch (Exception x) { LOG.warn("Failed to download Podcast from " + episode.getUrl(), x); episode.setStatus(PodcastStatus.ERROR); @@ -604,7 +604,6 @@ public class PodcastService { } finally { IOUtils.closeQuietly(in); IOUtils.closeQuietly(out); - client.getConnectionManager().shutdown(); } } diff --git a/libresonic-main/src/main/java/org/libresonic/player/service/SettingsService.java b/libresonic-main/src/main/java/org/libresonic/player/service/SettingsService.java index 4ca1eeef..44bcd48d 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/service/SettingsService.java +++ b/libresonic-main/src/main/java/org/libresonic/player/service/SettingsService.java @@ -43,12 +43,6 @@ import java.util.concurrent.TimeUnit; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; -import org.apache.http.client.HttpClient; -import org.apache.http.client.ResponseHandler; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.BasicResponseHandler; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.params.HttpConnectionParams; import org.libresonic.player.Logger; import org.libresonic.player.dao.AvatarDao; diff --git a/libresonic-main/src/main/java/org/libresonic/player/service/TranscodingService.java b/libresonic-main/src/main/java/org/libresonic/player/service/TranscodingService.java index 36871490..f28da40f 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/service/TranscodingService.java +++ b/libresonic-main/src/main/java/org/libresonic/player/service/TranscodingService.java @@ -329,8 +329,8 @@ public class TranscodingService { title = "Unknown Artist"; } - List result = new LinkedList(Arrays.asList(StringUtil.split(command))); - result.set(0, getTranscodeDirectory().getPath() + File.separatorChar + result.get(0)); + List result = new LinkedList<>(Arrays.asList(StringUtil.split(command))); + result.set(0, getExecutableName(result)); File tmpFile = null; @@ -381,6 +381,20 @@ public class TranscodingService { return new TranscodeInputStream(new ProcessBuilder(result), in, tmpFile); } + private String getExecutableName(List transcodeTokens) { + String executableName = transcodeTokens.get(0); + String transcodeDirectoryPath = getTranscodeDirectory().getPath() + File.separatorChar + executableName; + File file = new File(transcodeDirectoryPath); + if(file.exists()) { + if(!file.canExecute()) { + throw new RuntimeException("Transcoder is not executable at " + transcodeDirectoryPath); + } + return transcodeDirectoryPath; + } else { + return executableName; + } + } + /** * Returns an applicable transcoding for the given file and player, or null if no * transcoding should be done. diff --git a/libresonic-main/src/main/java/org/libresonic/player/service/VersionService.java b/libresonic-main/src/main/java/org/libresonic/player/service/VersionService.java index 25938cdf..c93e117d 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/service/VersionService.java +++ b/libresonic-main/src/main/java/org/libresonic/player/service/VersionService.java @@ -19,16 +19,6 @@ */ package org.libresonic.player.service; -import org.libresonic.player.Logger; -import org.libresonic.player.domain.Version; -import org.apache.commons.io.IOUtils; -import org.apache.http.client.HttpClient; -import org.apache.http.client.ResponseHandler; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.BasicResponseHandler; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.params.HttpConnectionParams; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -40,6 +30,17 @@ import java.util.Date; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.apache.commons.io.IOUtils; +import org.apache.http.client.ResponseHandler; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.BasicResponseHandler; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; + +import org.libresonic.player.Logger; +import org.libresonic.player.domain.Version; + /** * Provides version-related services, including functionality for determining whether a newer * version of Libresonic is available. @@ -231,25 +232,22 @@ public class VersionService { */ private void readLatestVersion() throws IOException { - HttpClient client = new DefaultHttpClient(); - HttpConnectionParams.setConnectionTimeout(client.getParams(), 10000); - HttpConnectionParams.setSoTimeout(client.getParams(), 10000); + RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(10000) + .setSocketTimeout(10000) + .build(); HttpGet method = new HttpGet(VERSION_URL + "?v=" + getLocalVersion()); + method.setConfig(requestConfig); String content; - try { - + try (CloseableHttpClient client = HttpClients.createDefault()) { ResponseHandler responseHandler = new BasicResponseHandler(); content = client.execute(method, responseHandler); - - } finally { - client.getConnectionManager().shutdown(); } - BufferedReader reader = new BufferedReader(new StringReader(content)); Pattern finalPattern = Pattern.compile("LIBRESONIC_FULL_VERSION_BEGIN(.*)LIBRESONIC_FULL_VERSION_END"); Pattern betaPattern = Pattern.compile("LIBRESONIC_BETA_VERSION_BEGIN(.*)LIBRESONIC_BETA_VERSION_END"); - try { + try (BufferedReader reader = new BufferedReader(new StringReader(content))) { String line = reader.readLine(); while (line != null) { Matcher finalMatcher = finalPattern.matcher(line); @@ -265,8 +263,6 @@ public class VersionService { line = reader.readLine(); } - } finally { - reader.close(); } } } diff --git a/libresonic-main/src/main/java/org/libresonic/player/service/sonos/SonosServiceRegistration.java b/libresonic-main/src/main/java/org/libresonic/player/service/sonos/SonosServiceRegistration.java index 593652a3..5215073b 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/service/sonos/SonosServiceRegistration.java +++ b/libresonic-main/src/main/java/org/libresonic/player/service/sonos/SonosServiceRegistration.java @@ -24,15 +24,15 @@ import java.util.ArrayList; import java.util.List; import org.apache.http.NameValuePair; -import org.apache.http.client.HttpClient; import org.apache.http.client.ResponseHandler; +import org.apache.http.client.config.RequestConfig; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.impl.client.BasicResponseHandler; -import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; -import org.apache.http.params.HttpConnectionParams; import org.libresonic.player.Logger; import org.libresonic.player.util.Pair; @@ -82,24 +82,24 @@ public class SonosServiceRegistration { for (Pair parameter : parameters) { params.add(new BasicNameValuePair(parameter.getFirst(), parameter.getSecond())); } - + RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(20 * 1000) // 20 seconds + .setSocketTimeout(20 * 1000) // 20 seconds + .build(); HttpPost request = new HttpPost(url); + request.setConfig(requestConfig); request.setEntity(new UrlEncodedFormEntity(params, StringUtil.ENCODING_UTF8)); return executeRequest(request); } private String executeRequest(HttpUriRequest request) throws IOException { - HttpClient client = new DefaultHttpClient(); - HttpConnectionParams.setConnectionTimeout(client.getParams(), 10000); - HttpConnectionParams.setSoTimeout(client.getParams(), 10000); - try { + + try (CloseableHttpClient client = HttpClients.createDefault()) { ResponseHandler responseHandler = new BasicResponseHandler(); return client.execute(request, responseHandler); - } finally { - client.getConnectionManager().shutdown(); } } } diff --git a/libresonic-main/src/main/resources/applicationContext-security.xml b/libresonic-main/src/main/resources/applicationContext-security.xml index 2bb5347a..f1903e76 100644 --- a/libresonic-main/src/main/resources/applicationContext-security.xml +++ b/libresonic-main/src/main/resources/applicationContext-security.xml @@ -56,7 +56,11 @@ - + + 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 771acad9..25cff3ab 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 @@ -100,6 +100,7 @@ playlist.clear = Clear playlist.shuffle = Shuffle playlist.repeat_on = Repeat is on playlist.repeat_off = Repeat is off +playlist.repeat_radio = Stop shuffle radio playlist.undo = Undo playlist.settings = Settings playlist.more = More actions... @@ -233,7 +234,8 @@ 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.addtoplaylist = Add to current playlist +more.random.add = Add to queue +more.random.radio = Shuffle radio more.random.any = Any more.random.format = Format more.random.genre = Genre diff --git a/libresonic-main/src/main/webapp/WEB-INF/jsp/login.jsp b/libresonic-main/src/main/webapp/WEB-INF/jsp/login.jsp index 6209b414..4218a07a 100644 --- a/libresonic-main/src/main/webapp/WEB-INF/jsp/login.jsp +++ b/libresonic-main/src/main/webapp/WEB-INF/jsp/login.jsp @@ -35,6 +35,10 @@ " tabindex="4"> + + + + 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 3d708bf5..cc23dd5a 100644 --- a/libresonic-main/src/main/webapp/WEB-INF/jsp/more.jsp +++ b/libresonic-main/src/main/webapp/WEB-INF/jsp/more.jsp @@ -232,24 +232,13 @@ - - - - - "> + "> + "> - - - - - - - - diff --git a/libresonic-main/src/main/webapp/WEB-INF/jsp/playQueue.jsp b/libresonic-main/src/main/webapp/WEB-INF/jsp/playQueue.jsp index c8917a44..aa48c89d 100644 --- a/libresonic-main/src/main/webapp/WEB-INF/jsp/playQueue.jsp +++ b/libresonic-main/src/main/webapp/WEB-INF/jsp/playQueue.jsp @@ -37,6 +37,7 @@ var songs = null; var currentStreamUrl = null; var repeatEnabled = false; + var radioEnabled = false; var isVisible = ${model.autoHide ? 'false' : 'true'}; var CastPlayer = new CastPlayer(); var ignore = false; @@ -269,7 +270,13 @@ } function onNext(wrap) { var index = parseInt(getCurrentSongIndex()) + 1; - if (wrap) { + if (radioEnabled && index >= songs.length) { + playQueueService.reloadSearchCriteria(function(playQueue) { + playQueueCallback(playQueue); + onSkip(index); + }); + return; + } else if (wrap) { index = index % songs.length; } onSkip(index); @@ -402,14 +409,20 @@ function playQueueCallback(playQueue) { songs = playQueue.entries; repeatEnabled = playQueue.repeatEnabled; + radioEnabled = playQueue.radioEnabled; if ($("#start")) { $("#start").toggle(!playQueue.stopEnabled); $("#stop").toggle(playQueue.stopEnabled); } if ($("#toggleRepeat")) { - var text = repeatEnabled ? "" : ""; - $("#toggleRepeat").html(text); + if (radioEnabled) { + $("#toggleRepeat").html(""); + } else if (repeatEnabled) { + $("#toggleRepeat").html(""); + } else { + $("#toggleRepeat").html(""); + } } if (songs.length == 0) { diff --git a/libresonic-rest-api/pom.xml b/libresonic-rest-api/pom.xml index 420ba6c7..e95a1a8d 100644 --- a/libresonic-rest-api/pom.xml +++ b/libresonic-rest-api/pom.xml @@ -8,7 +8,7 @@ org.libresonic.player libresonic - 6.2.spring4-SNAPSHOT + 6.2.beta1.spring4 diff --git a/libresonic-sonos-api/pom.xml b/libresonic-sonos-api/pom.xml index 61daab0a..1012b990 100644 --- a/libresonic-sonos-api/pom.xml +++ b/libresonic-sonos-api/pom.xml @@ -8,7 +8,7 @@ org.libresonic.player libresonic - 6.2.spring4-SNAPSHOT + 6.2.beta1.spring4 diff --git a/pom.xml b/pom.xml index 3d6bdb47..7d64bd46 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 org.libresonic.player libresonic - 6.2.spring4-SNAPSHOT + 6.2.beta1.spring4 Libresonic pom