Merge remote-tracking branch 'upstream/develop' into biconou_develop_PR_SpringBoot

Conflicts:
	libresonic-main/src/main/resources/applicationContext-security.xml
master
Rémi Cocula 8 years ago
commit c0ee1ef786
  1. 0
      documentation/BUILD.md
  2. 0
      documentation/MIGRATE.md
  3. 109
      documentation/PROXY.md
  4. 2
      libresonic-assembly/pom.xml
  5. 2
      libresonic-booter/pom.xml
  6. 2
      libresonic-installer-debian/pom.xml
  7. 2
      libresonic-installer-mac/pom.xml
  8. 2
      libresonic-installer-rpm/pom.xml
  9. 2
      libresonic-installer-windows/pom.xml
  10. 6
      libresonic-main/pom.xml
  11. 110
      libresonic-main/src/main/java/org/libresonic/player/ajax/CoverArtService.java
  12. 20
      libresonic-main/src/main/java/org/libresonic/player/ajax/LyricsService.java
  13. 10
      libresonic-main/src/main/java/org/libresonic/player/ajax/PlayQueueInfo.java
  14. 24
      libresonic-main/src/main/java/org/libresonic/player/ajax/PlayQueueService.java
  15. 34
      libresonic-main/src/main/java/org/libresonic/player/controller/ProxyController.java
  16. 2
      libresonic-main/src/main/java/org/libresonic/player/controller/RandomPlayQueueController.java
  17. 2
      libresonic-main/src/main/java/org/libresonic/player/dao/schema/hsql/Schema62.java
  18. 11
      libresonic-main/src/main/java/org/libresonic/player/domain/PlayQueue.java
  19. 24
      libresonic-main/src/main/java/org/libresonic/player/service/AudioScrobblerService.java
  20. 58
      libresonic-main/src/main/java/org/libresonic/player/service/NetworkService.java
  21. 157
      libresonic-main/src/main/java/org/libresonic/player/service/PodcastService.java
  22. 6
      libresonic-main/src/main/java/org/libresonic/player/service/SettingsService.java
  23. 18
      libresonic-main/src/main/java/org/libresonic/player/service/TranscodingService.java
  24. 40
      libresonic-main/src/main/java/org/libresonic/player/service/VersionService.java
  25. 20
      libresonic-main/src/main/java/org/libresonic/player/service/sonos/SonosServiceRegistration.java
  26. 6
      libresonic-main/src/main/resources/applicationContext-security.xml
  27. 4
      libresonic-main/src/main/resources/org/libresonic/player/i18n/ResourceBundle_en.properties
  28. 4
      libresonic-main/src/main/webapp/WEB-INF/jsp/login.jsp
  29. 15
      libresonic-main/src/main/webapp/WEB-INF/jsp/more.jsp
  30. 19
      libresonic-main/src/main/webapp/WEB-INF/jsp/playQueue.jsp
  31. 2
      libresonic-rest-api/pom.xml
  32. 2
      libresonic-sonos-api/pom.xml
  33. 2
      pom.xml

@ -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
<VirtualHost *:80>
ServerName example.com
ErrorDocument 404 /404.html
DocumentRoot /var/www
ProxyPass /libresonic http://localhost:4040/libresonic
ProxyPassReverse /libresonic http://localhost:4040/libresonic
</VirtualHost>
```
### 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
```

@ -9,7 +9,7 @@
<parent> <parent>
<groupId>org.libresonic.player</groupId> <groupId>org.libresonic.player</groupId>
<artifactId>libresonic</artifactId> <artifactId>libresonic</artifactId>
<version>6.2.spring4-SNAPSHOT</version> <version>6.2.beta1.spring4</version>
</parent> </parent>
<dependencies> <dependencies>

@ -8,7 +8,7 @@
<parent> <parent>
<groupId>org.libresonic.player</groupId> <groupId>org.libresonic.player</groupId>
<artifactId>libresonic</artifactId> <artifactId>libresonic</artifactId>
<version>6.2.spring4-SNAPSHOT</version> <version>6.2.beta1.spring4</version>
</parent> </parent>
<dependencies> <dependencies>

@ -9,7 +9,7 @@
<parent> <parent>
<groupId>org.libresonic.player</groupId> <groupId>org.libresonic.player</groupId>
<artifactId>libresonic</artifactId> <artifactId>libresonic</artifactId>
<version>6.2.spring4-SNAPSHOT</version> <version>6.2.beta1.spring4</version>
</parent> </parent>
<profiles> <profiles>

@ -9,7 +9,7 @@
<parent> <parent>
<groupId>org.libresonic.player</groupId> <groupId>org.libresonic.player</groupId>
<artifactId>libresonic</artifactId> <artifactId>libresonic</artifactId>
<version>6.2.spring4-SNAPSHOT</version> <version>6.2.beta1.spring4</version>
</parent> </parent>
<dependencies> <dependencies>

@ -9,7 +9,7 @@
<parent> <parent>
<groupId>org.libresonic.player</groupId> <groupId>org.libresonic.player</groupId>
<artifactId>libresonic</artifactId> <artifactId>libresonic</artifactId>
<version>6.2.spring4-SNAPSHOT</version> <version>6.2.beta1.spring4</version>
</parent> </parent>
<profiles> <profiles>

@ -9,7 +9,7 @@
<parent> <parent>
<groupId>org.libresonic.player</groupId> <groupId>org.libresonic.player</groupId>
<artifactId>libresonic</artifactId> <artifactId>libresonic</artifactId>
<version>6.2.spring4-SNAPSHOT</version> <version>6.2.beta1.spring4</version>
</parent> </parent>
<properties> <properties>

@ -9,7 +9,7 @@
<parent> <parent>
<groupId>org.libresonic.player</groupId> <groupId>org.libresonic.player</groupId>
<artifactId>libresonic</artifactId> <artifactId>libresonic</artifactId>
<version>6.2.spring4-SNAPSHOT</version> <version>6.2.beta1.spring4</version>
</parent> </parent>
<properties> <properties>
@ -136,13 +136,13 @@
<dependency> <dependency>
<groupId>org.apache.httpcomponents</groupId> <groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId> <artifactId>httpcore</artifactId>
<version>4.2.4</version> <version>4.4.5</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.httpcomponents</groupId> <groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId> <artifactId>httpclient</artifactId>
<version>4.2.4</version> <version>4.5.2</version>
</dependency> </dependency>
<dependency> <dependency>

@ -25,11 +25,11 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse; import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.HttpClient; import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.params.HttpConnectionParams; import org.apache.http.impl.client.HttpClients;
import org.libresonic.player.Logger; import org.libresonic.player.Logger;
import org.libresonic.player.domain.MediaFile; import org.libresonic.player.domain.MediaFile;
@ -72,69 +72,69 @@ public class CoverArtService {
private void saveCoverArt(String path, String url) throws Exception { private void saveCoverArt(String path, String url) throws Exception {
InputStream input = null; InputStream input = null;
OutputStream output = null; OutputStream output = null;
HttpClient client = new DefaultHttpClient();
try { try (CloseableHttpClient client = HttpClients.createDefault()) {
HttpConnectionParams.setConnectionTimeout(client.getParams(), 20 * 1000); // 20 seconds RequestConfig requestConfig = RequestConfig.custom()
HttpConnectionParams.setSoTimeout(client.getParams(), 20 * 1000); // 20 seconds .setConnectTimeout(20 * 1000) // 20 seconds
.setSocketTimeout(20 * 1000) // 20 seconds
.build();
HttpGet method = new HttpGet(url); 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); // Check permissions.
input = response.getEntity().getContent(); File newCoverFile = new File(path, "cover." + suffix);
if (!securityService.isWriteAllowed(newCoverFile)) {
// Attempt to resolve proper suffix. throw new Exception("Permission denied: " + StringUtil.toHtml(newCoverFile.getPath()));
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());
// Rename existing cover files if new cover file is not the preferred. // If file exists, create a backup.
try { backup(newCoverFile, new File(path, "cover." + suffix + ".backup"));
while (true) {
File coverFile = mediaFileService.getCoverArt(dir); // Write file.
if (coverFile != null && !isMediaFile(coverFile) && !newCoverFile.equals(coverFile)) { output = new FileOutputStream(newCoverFile);
if (!coverFile.renameTo(new File(coverFile.getCanonicalPath() + ".old"))) { IOUtils.copy(input, output);
LOG.warn("Unable to rename old image file " + coverFile);
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; 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 { } finally {
IOUtils.closeQuietly(input); IOUtils.closeQuietly(input);
IOUtils.closeQuietly(output); IOUtils.closeQuietly(output);
client.getConnectionManager().shutdown();
} }
} }

@ -24,12 +24,12 @@ import java.io.StringReader;
import java.net.SocketException; import java.net.SocketException;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler; import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicResponseHandler; 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.params.HttpConnectionParams; import org.apache.http.impl.client.HttpClients;
import org.jdom.Document; import org.jdom.Document;
import org.jdom.Element; import org.jdom.Element;
import org.jdom.Namespace; import org.jdom.Namespace;
@ -92,17 +92,15 @@ public class LyricsService {
} }
private String executeGetRequest(String url) throws IOException { private String executeGetRequest(String url) throws IOException {
HttpClient client = new DefaultHttpClient(); RequestConfig requestConfig = RequestConfig.custom()
HttpConnectionParams.setConnectionTimeout(client.getParams(), 15000); .setConnectTimeout(15000)
HttpConnectionParams.setSoTimeout(client.getParams(), 15000); .setSocketTimeout(15000)
.build();
HttpGet method = new HttpGet(url); HttpGet method = new HttpGet(url);
try { method.setConfig(requestConfig);
try (CloseableHttpClient client = HttpClients.createDefault()) {
ResponseHandler<String> responseHandler = new BasicResponseHandler(); ResponseHandler<String> responseHandler = new BasicResponseHandler();
return client.execute(method, responseHandler); return client.execute(method, responseHandler);
} finally {
client.getConnectionManager().shutdown();
} }
} }
} }

@ -33,15 +33,17 @@ public class PlayQueueInfo {
private final List<Entry> entries; private final List<Entry> entries;
private final boolean stopEnabled; private final boolean stopEnabled;
private final boolean repeatEnabled; private final boolean repeatEnabled;
private final boolean radioEnabled;
private final boolean sendM3U; private final boolean sendM3U;
private final float gain; private final float gain;
private int startPlayerAt = -1; private int startPlayerAt = -1;
private long startPlayerAtPosition; // millis private long startPlayerAtPosition; // millis
public PlayQueueInfo(List<Entry> entries, boolean stopEnabled, boolean repeatEnabled, boolean sendM3U, float gain) { public PlayQueueInfo(List<Entry> entries, boolean stopEnabled, boolean repeatEnabled, boolean radioEnabled, boolean sendM3U, float gain) {
this.entries = entries; this.entries = entries;
this.stopEnabled = stopEnabled; this.stopEnabled = stopEnabled;
this.repeatEnabled = repeatEnabled; this.repeatEnabled = repeatEnabled;
this.radioEnabled = radioEnabled;
this.sendM3U = sendM3U; this.sendM3U = sendM3U;
this.gain = gain; this.gain = gain;
} }
@ -72,6 +74,10 @@ public class PlayQueueInfo {
return repeatEnabled; return repeatEnabled;
} }
public boolean isRadioEnabled() {
return radioEnabled;
}
public float getGain() { public float getGain() {
return gain; return gain;
} }
@ -215,4 +221,4 @@ public class PlayQueueInfo {
return remoteCoverArtUrl; return remoteCoverArtUrl;
} }
} }
} }

@ -148,6 +148,18 @@ public class PlayQueueService {
return convert(request, player, serverSidePlaylist, offset); 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) { public void savePlayQueue(int currentSongIndex, long positionMillis) {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest(); HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse(); HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
@ -581,7 +593,13 @@ public class PlayQueueService {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest(); HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse(); HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
Player player = getCurrentPlayer(request, response); 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); return convert(request, player, false);
} }
@ -668,7 +686,7 @@ public class PlayQueueService {
} }
boolean isStopEnabled = playQueue.getStatus() == PlayQueue.Status.PLAYING && !player.isExternalWithPlaylist(); boolean isStopEnabled = playQueue.getStatus() == PlayQueue.Status.PLAYING && !player.isExternalWithPlaylist();
float gain = jukeboxService.getGain(); 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) { private String formatFileSize(Long fileSize, Locale locale) {
@ -751,4 +769,4 @@ public class PlayQueueService {
public void setPlaylistService(PlaylistService playlistService) { public void setPlaylistService(PlaylistService playlistService) {
this.playlistService = playlistService; this.playlistService = playlistService;
} }
} }

@ -25,12 +25,12 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus; 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.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.params.HttpConnectionParams; import org.apache.http.impl.client.HttpClients;
import org.springframework.web.bind.ServletRequestUtils; import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller; 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 { public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
String url = ServletRequestUtils.getRequiredStringParameter(request, "url"); String url = ServletRequestUtils.getRequiredStringParameter(request, "url");
HttpClient client = new DefaultHttpClient(); RequestConfig requestConfig = RequestConfig.custom()
HttpConnectionParams.setConnectionTimeout(client.getParams(), 15000); .setConnectTimeout(15000)
HttpConnectionParams.setSoTimeout(client.getParams(), 15000); .setSocketTimeout(15000)
.build();
HttpGet method = new HttpGet(url); HttpGet method = new HttpGet(url);
method.setConfig(requestConfig);
InputStream in = null; InputStream in = null;
try { try (CloseableHttpClient client = HttpClients.createDefault()) {
HttpResponse resp = client.execute(method); try (CloseableHttpResponse resp = client.execute(method)) {
int statusCode = resp.getStatusLine().getStatusCode(); int statusCode = resp.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) { if (statusCode != HttpStatus.SC_OK) {
response.sendError(statusCode); response.sendError(statusCode);
} else { } else {
in = resp.getEntity().getContent(); in = resp.getEntity().getContent();
IOUtils.copy(in, response.getOutputStream()); IOUtils.copy(in, response.getOutputStream());
}
} }
} finally { } finally {
IOUtils.closeQuietly(in); IOUtils.closeQuietly(in);
client.getConnectionManager().shutdown();
} }
return null; return null;
} }

@ -199,7 +199,7 @@ public class RandomPlayQueueController extends ParameterizableViewController {
List<MusicFolder> musicFolders = getMusicFolders(request); List<MusicFolder> musicFolders = getMusicFolders(request);
// Do we add to the current playlist or do we replace it? // 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 // Search the database using these criteria
RandomSearchCriteria criteria = new RandomSearchCriteria( RandomSearchCriteria criteria = new RandomSearchCriteria(

@ -39,7 +39,7 @@ public class Schema62 extends Schema {
template.execute("insert into version values (27)"); 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."); 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"); 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."); LOG.info("Database column 'player.m3u_bom_enabled' was added successfully.");

@ -368,6 +368,15 @@ public class PlayQueue {
this.repeatEnabled = repeatEnabled; 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. * Revert the last operation.
*/ */
@ -455,4 +464,4 @@ public class PlayQueue {
ARTIST, ARTIST,
ALBUM ALBUM
} }
} }

@ -29,16 +29,16 @@ import java.util.concurrent.LinkedBlockingQueue;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import org.apache.http.NameValuePair; import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler; 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.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.BasicResponseHandler; 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.message.BasicNameValuePair;
import org.apache.http.params.HttpConnectionParams;
import org.libresonic.player.Logger; import org.libresonic.player.Logger;
import org.libresonic.player.domain.MediaFile; import org.libresonic.player.domain.MediaFile;
@ -62,7 +62,10 @@ public class AudioScrobblerService {
private final LinkedBlockingQueue<RegistrationData> queue = new LinkedBlockingQueue<RegistrationData>(); private final LinkedBlockingQueue<RegistrationData> queue = new LinkedBlockingQueue<RegistrationData>();
private SettingsService settingsService; 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 * 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 { 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<String, String> parameters) throws IOException { private String[] executePostRequest(String url, Map<String, String> parameters) throws IOException {
@ -245,22 +250,17 @@ public class AudioScrobblerService {
HttpPost request = new HttpPost(url); HttpPost request = new HttpPost(url);
request.setEntity(new UrlEncodedFormEntity(params, StringUtil.ENCODING_UTF8)); request.setEntity(new UrlEncodedFormEntity(params, StringUtil.ENCODING_UTF8));
request.setConfig(requestConfig);
return executeRequest(request); return executeRequest(request);
} }
private String[] executeRequest(HttpUriRequest request) throws IOException { 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<String> responseHandler = new BasicResponseHandler(); ResponseHandler<String> responseHandler = new BasicResponseHandler();
String response = client.execute(request, responseHandler); String response = client.execute(request, responseHandler);
return response.split("\\n"); return response.split("\\n");
} finally {
client.getConnectionManager().shutdown();
} }
} }

@ -28,19 +28,18 @@ import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; 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.HttpStatus;
import org.apache.http.NameValuePair; import org.apache.http.NameValuePair;
import org.apache.http.StatusLine; 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.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.BasicResponseHandler; 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.message.BasicNameValuePair;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.util.EntityUtils; import org.apache.http.util.EntityUtils;
import org.libresonic.player.Logger; import org.libresonic.player.Logger;
@ -250,34 +249,32 @@ public class NetworkService {
params.add(new BasicNameValuePair("licenseHolder", settingsService.getLicenseEmail())); 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..."); urlRedirectionStatus.setText(enable ? "Registering web address..." : "Unregistering web address...");
request.setEntity(new UrlEncodedFormEntity(params, StringUtil.ENCODING_UTF8)); request.setEntity(new UrlEncodedFormEntity(params, StringUtil.ENCODING_UTF8));
HttpResponse response = client.execute(request); try (CloseableHttpResponse response = client.execute(request)) {
StatusLine status = response.getStatusLine(); StatusLine status = response.getStatusLine();
switch (status.getStatusCode()) { switch (status.getStatusCode()) {
case HttpStatus.SC_BAD_REQUEST: case HttpStatus.SC_BAD_REQUEST:
urlRedirectionStatus.setText(EntityUtils.toString(response.getEntity())); urlRedirectionStatus.setText(EntityUtils.toString(response.getEntity()));
testUrlRedirection = false; testUrlRedirection = false;
break; break;
case HttpStatus.SC_OK: case HttpStatus.SC_OK:
urlRedirectionStatus.setText(enable ? "Successfully registered web address." : "Web address disabled."); urlRedirectionStatus.setText(enable ? "Successfully registered web address." : "Web address disabled.");
break; break;
default: default:
testUrlRedirection = false; testUrlRedirection = false;
throw new IOException(status.getStatusCode() + " " + status.getReasonPhrase()); throw new IOException(status.getStatusCode() + " " + status.getReasonPhrase());
}
} }
} catch (Throwable x) { } catch (Throwable x) {
LOG.warn(enable ? "Failed to register web address." : "Failed to unregister web address.", 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() + urlRedirectionStatus.setText(enable ? ("Failed to register web address. " + x.getMessage() +
" (" + x.getClass().getSimpleName() + ")") : "Web address disabled."); " (" + x.getClass().getSimpleName() + ")") : "Web address disabled.");
} finally {
client.getConnectionManager().shutdown();
} }
// Test redirection, but only once. // Test redirection, but only once.
@ -305,20 +302,19 @@ public class NetworkService {
} }
HttpGet request = new HttpGet(url); 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..."); urlRedirectionStatus.setText("Testing web address " + urlToTest + ". Please wait...");
String response = client.execute(request, new BasicResponseHandler()); String response = client.execute(request, new BasicResponseHandler());
urlRedirectionStatus.setText(response); urlRedirectionStatus.setText(response);
} catch (Throwable x) { } catch (Throwable x) {
LOG.warn("Failed to test web address.", x); LOG.warn("Failed to test web address.", x);
urlRedirectionStatus.setText("Failed to test web address. " + x.getMessage() + " (" + x.getClass().getSimpleName() + ")"); urlRedirectionStatus.setText("Failed to test web address. " + x.getMessage() + " (" + x.getClass().getSimpleName() + ")");
} finally {
client.getConnectionManager().shutdown();
} }
} }
} }

@ -39,25 +39,25 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit; 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.FilenameUtils;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.http.Header; import org.apache.http.Header;
import org.apache.http.HttpResponse; 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.client.methods.HttpGet;
import org.apache.http.entity.ContentType; import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.params.HttpConnectionParams; import org.apache.http.impl.client.HttpClients;
import org.jdom.Document; import org.jdom.Document;
import org.jdom.Element; import org.jdom.Element;
import org.jdom.Namespace; import org.jdom.Namespace;
import org.jdom.input.SAXBuilder; 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.Logger;
import org.libresonic.player.dao.PodcastDao; import org.libresonic.player.dao.PodcastDao;
import org.libresonic.player.domain.MediaFile; import org.libresonic.player.domain.MediaFile;
@ -302,33 +302,34 @@ public class PodcastService {
@SuppressWarnings({"unchecked"}) @SuppressWarnings({"unchecked"})
private void doRefreshChannel(PodcastChannel channel, boolean downloadEpisodes) { private void doRefreshChannel(PodcastChannel channel, boolean downloadEpisodes) {
InputStream in = null; InputStream in = null;
HttpClient client = new DefaultHttpClient();
try { try (CloseableHttpClient client = HttpClients.createDefault()) {
channel.setStatus(PodcastStatus.DOWNLOADING); channel.setStatus(PodcastStatus.DOWNLOADING);
channel.setErrorMessage(null); channel.setErrorMessage(null);
podcastDao.updateChannel(channel); podcastDao.updateChannel(channel);
RequestConfig requestConfig = RequestConfig.custom()
HttpConnectionParams.setConnectionTimeout(client.getParams(), 2 * 60 * 1000); // 2 minutes .setConnectTimeout(2 * 60 * 1000) // 2 minutes
HttpConnectionParams.setSoTimeout(client.getParams(), 10 * 60 * 1000); // 10 minutes .setSocketTimeout(10 * 60 * 1000) // 10 minutes
.build();
HttpGet method = new HttpGet(channel.getUrl()); HttpGet method = new HttpGet(channel.getUrl());
method.setConfig(requestConfig);
HttpResponse response = client.execute(method); try (CloseableHttpResponse response = client.execute(method)) {
in = response.getEntity().getContent(); in = response.getEntity().getContent();
Document document = new SAXBuilder().build(in);
Element channelElement = document.getRootElement().getChild("channel");
channel.setTitle(StringUtil.removeMarkup(channelElement.getChildTextTrim("title"))); Document document = new SAXBuilder().build(in);
channel.setDescription(StringUtil.removeMarkup(channelElement.getChildTextTrim("description"))); Element channelElement = document.getRootElement().getChild("channel");
channel.setImageUrl(getChannelImageUrl(channelElement));
channel.setStatus(PodcastStatus.COMPLETED);
channel.setErrorMessage(null);
podcastDao.updateChannel(channel);
downloadImage(channel); channel.setTitle(StringUtil.removeMarkup(channelElement.getChildTextTrim("title")));
refreshEpisodes(channel, channelElement.getChildren("item")); 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) { } catch (Exception x) {
LOG.warn("Failed to get/parse RSS file for Podcast channel " + channel.getUrl(), x); LOG.warn("Failed to get/parse RSS file for Podcast channel " + channel.getUrl(), x);
channel.setStatus(PodcastStatus.ERROR); channel.setStatus(PodcastStatus.ERROR);
@ -336,7 +337,6 @@ public class PodcastService {
podcastDao.updateChannel(channel); podcastDao.updateChannel(channel);
} finally { } finally {
IOUtils.closeQuietly(in); IOUtils.closeQuietly(in);
client.getConnectionManager().shutdown();
} }
if (downloadEpisodes) { if (downloadEpisodes) {
@ -349,10 +349,9 @@ public class PodcastService {
} }
private void downloadImage(PodcastChannel channel) { private void downloadImage(PodcastChannel channel) {
HttpClient client = new DefaultHttpClient();
InputStream in = null; InputStream in = null;
OutputStream out = null; OutputStream out = null;
try { try(CloseableHttpClient client = HttpClients.createDefault()) {
String imageUrl = channel.getImageUrl(); String imageUrl = channel.getImageUrl();
if (imageUrl == null) { if (imageUrl == null) {
return; return;
@ -367,17 +366,17 @@ public class PodcastService {
} }
HttpGet method = new HttpGet(imageUrl); HttpGet method = new HttpGet(imageUrl);
HttpResponse response = client.execute(method); try (CloseableHttpResponse response = client.execute(method)) {
in = response.getEntity().getContent(); in = response.getEntity().getContent();
out = new FileOutputStream(new File(dir, "cover." + getCoverArtSuffix(response))); out = new FileOutputStream(new File(dir, "cover." + getCoverArtSuffix(response)));
IOUtils.copy(in, out); IOUtils.copy(in, out);
mediaFileService.refreshMediaFile(channelMediaFile); mediaFileService.refreshMediaFile(channelMediaFile);
}
} catch (Exception x) { } catch (Exception x) {
LOG.warn("Failed to download cover art for podcast channel '" + channel.getTitle() + "': " + x, x); LOG.warn("Failed to download cover art for podcast channel '" + channel.getTitle() + "': " + x, x);
} finally { } finally {
IOUtils.closeQuietly(in); IOUtils.closeQuietly(in);
IOUtils.closeQuietly(out); IOUtils.closeQuietly(out);
client.getConnectionManager().shutdown();
} }
} }
@ -534,68 +533,69 @@ public class PodcastService {
LOG.info("Starting to download Podcast from " + episode.getUrl()); LOG.info("Starting to download Podcast from " + episode.getUrl());
HttpClient client = new DefaultHttpClient(); try (CloseableHttpClient client = HttpClients.createDefault()) {
try {
if (!settingsService.getLicenseInfo().isLicenseOrTrialValid()) { if (!settingsService.getLicenseInfo().isLicenseOrTrialValid()) {
throw new Exception("Sorry, the trial period is expired."); throw new Exception("Sorry, the trial period is expired.");
} }
PodcastChannel channel = getChannel(episode.getChannelId()); PodcastChannel channel = getChannel(episode.getChannelId());
RequestConfig requestConfig = RequestConfig.custom()
HttpConnectionParams.setConnectionTimeout(client.getParams(), 2 * 60 * 1000); // 2 minutes .setConnectTimeout(2 * 60 * 1000) // 2 minutes
HttpConnectionParams.setSoTimeout(client.getParams(), 10 * 60 * 1000); // 10 minutes .setSocketTimeout(10 * 60 * 1000) // 10 minutes
.build();
HttpGet method = new HttpGet(episode.getUrl()); HttpGet method = new HttpGet(episode.getUrl());
method.setConfig(requestConfig);
HttpResponse response = client.execute(method); try (CloseableHttpResponse response = client.execute(method)) {
in = response.getEntity().getContent(); in = response.getEntity().getContent();
File file = getFile(channel, episode); File file = getFile(channel, episode);
out = new FileOutputStream(file); out = new FileOutputStream(file);
episode.setStatus(PodcastStatus.DOWNLOADING); episode.setStatus(PodcastStatus.DOWNLOADING);
episode.setBytesDownloaded(0L); episode.setBytesDownloaded(0L);
episode.setErrorMessage(null); episode.setErrorMessage(null);
episode.setPath(file.getPath()); episode.setPath(file.getPath());
podcastDao.updateEpisode(episode); podcastDao.updateEpisode(episode);
byte[] buffer = new byte[4096]; byte[] buffer = new byte[4096];
long bytesDownloaded = 0; long bytesDownloaded = 0;
int n; int n;
long nextLogCount = 30000L; long nextLogCount = 30000L;
while ((n = in.read(buffer)) != -1) { while ((n = in.read(buffer)) != -1) {
out.write(buffer, 0, n); out.write(buffer, 0, n);
bytesDownloaded += n; bytesDownloaded += n;
if (bytesDownloaded > nextLogCount) { if (bytesDownloaded > nextLogCount) {
episode.setBytesDownloaded(bytesDownloaded); episode.setBytesDownloaded(bytesDownloaded);
nextLogCount += 30000L; nextLogCount += 30000L;
// Abort download if episode was deleted by user. // Abort download if episode was deleted by user.
if (isEpisodeDeleted(episode)) { if (isEpisodeDeleted(episode)) {
break; break;
}
podcastDao.updateEpisode(episode);
} }
podcastDao.updateEpisode(episode);
} }
}
if (isEpisodeDeleted(episode)) { if (isEpisodeDeleted(episode)) {
LOG.info("Podcast " + episode.getUrl() + " was deleted. Aborting download."); LOG.info("Podcast " + episode.getUrl() + " was deleted. Aborting download.");
IOUtils.closeQuietly(out); IOUtils.closeQuietly(out);
file.delete(); file.delete();
} else { } else {
addMediaFileIdToEpisodes(Arrays.asList(episode)); addMediaFileIdToEpisodes(Arrays.asList(episode));
episode.setBytesDownloaded(bytesDownloaded); episode.setBytesDownloaded(bytesDownloaded);
podcastDao.updateEpisode(episode); podcastDao.updateEpisode(episode);
LOG.info("Downloaded " + bytesDownloaded + " bytes from Podcast " + episode.getUrl()); LOG.info("Downloaded " + bytesDownloaded + " bytes from Podcast " + episode.getUrl());
IOUtils.closeQuietly(out); IOUtils.closeQuietly(out);
updateTags(file, episode); updateTags(file, episode);
episode.setStatus(PodcastStatus.COMPLETED); episode.setStatus(PodcastStatus.COMPLETED);
podcastDao.updateEpisode(episode); podcastDao.updateEpisode(episode);
deleteObsoleteEpisodes(channel); deleteObsoleteEpisodes(channel);
}
} }
} catch (Exception x) { } catch (Exception x) {
LOG.warn("Failed to download Podcast from " + episode.getUrl(), x); LOG.warn("Failed to download Podcast from " + episode.getUrl(), x);
episode.setStatus(PodcastStatus.ERROR); episode.setStatus(PodcastStatus.ERROR);
@ -604,7 +604,6 @@ public class PodcastService {
} finally { } finally {
IOUtils.closeQuietly(in); IOUtils.closeQuietly(in);
IOUtils.closeQuietly(out); IOUtils.closeQuietly(out);
client.getConnectionManager().shutdown();
} }
} }

@ -43,12 +43,6 @@ import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils; 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.Logger;
import org.libresonic.player.dao.AvatarDao; import org.libresonic.player.dao.AvatarDao;

@ -329,8 +329,8 @@ public class TranscodingService {
title = "Unknown Artist"; title = "Unknown Artist";
} }
List<String> result = new LinkedList<String>(Arrays.asList(StringUtil.split(command))); List<String> result = new LinkedList<>(Arrays.asList(StringUtil.split(command)));
result.set(0, getTranscodeDirectory().getPath() + File.separatorChar + result.get(0)); result.set(0, getExecutableName(result));
File tmpFile = null; File tmpFile = null;
@ -381,6 +381,20 @@ public class TranscodingService {
return new TranscodeInputStream(new ProcessBuilder(result), in, tmpFile); return new TranscodeInputStream(new ProcessBuilder(result), in, tmpFile);
} }
private String getExecutableName(List<String> 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 <code>null</code> if no * Returns an applicable transcoding for the given file and player, or <code>null</code> if no
* transcoding should be done. * transcoding should be done.

@ -19,16 +19,6 @@
*/ */
package org.libresonic.player.service; 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.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -40,6 +30,17 @@ import java.util.Date;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; 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 * Provides version-related services, including functionality for determining whether a newer
* version of Libresonic is available. * version of Libresonic is available.
@ -231,25 +232,22 @@ public class VersionService {
*/ */
private void readLatestVersion() throws IOException { private void readLatestVersion() throws IOException {
HttpClient client = new DefaultHttpClient(); RequestConfig requestConfig = RequestConfig.custom()
HttpConnectionParams.setConnectionTimeout(client.getParams(), 10000); .setConnectTimeout(10000)
HttpConnectionParams.setSoTimeout(client.getParams(), 10000); .setSocketTimeout(10000)
.build();
HttpGet method = new HttpGet(VERSION_URL + "?v=" + getLocalVersion()); HttpGet method = new HttpGet(VERSION_URL + "?v=" + getLocalVersion());
method.setConfig(requestConfig);
String content; String content;
try { try (CloseableHttpClient client = HttpClients.createDefault()) {
ResponseHandler<String> responseHandler = new BasicResponseHandler(); ResponseHandler<String> responseHandler = new BasicResponseHandler();
content = client.execute(method, responseHandler); 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 finalPattern = Pattern.compile("LIBRESONIC_FULL_VERSION_BEGIN(.*)LIBRESONIC_FULL_VERSION_END");
Pattern betaPattern = Pattern.compile("LIBRESONIC_BETA_VERSION_BEGIN(.*)LIBRESONIC_BETA_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(); String line = reader.readLine();
while (line != null) { while (line != null) {
Matcher finalMatcher = finalPattern.matcher(line); Matcher finalMatcher = finalPattern.matcher(line);
@ -265,8 +263,6 @@ public class VersionService {
line = reader.readLine(); line = reader.readLine();
} }
} finally {
reader.close();
} }
} }
} }

@ -24,15 +24,15 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.http.NameValuePair; import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler; 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.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.BasicResponseHandler; 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.message.BasicNameValuePair;
import org.apache.http.params.HttpConnectionParams;
import org.libresonic.player.Logger; import org.libresonic.player.Logger;
import org.libresonic.player.util.Pair; import org.libresonic.player.util.Pair;
@ -82,24 +82,24 @@ public class SonosServiceRegistration {
for (Pair<String, String> parameter : parameters) { for (Pair<String, String> parameter : parameters) {
params.add(new BasicNameValuePair(parameter.getFirst(), parameter.getSecond())); 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); HttpPost request = new HttpPost(url);
request.setConfig(requestConfig);
request.setEntity(new UrlEncodedFormEntity(params, StringUtil.ENCODING_UTF8)); request.setEntity(new UrlEncodedFormEntity(params, StringUtil.ENCODING_UTF8));
return executeRequest(request); return executeRequest(request);
} }
private String executeRequest(HttpUriRequest request) throws IOException { 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<String> responseHandler = new BasicResponseHandler(); ResponseHandler<String> responseHandler = new BasicResponseHandler();
return client.execute(request, responseHandler); return client.execute(request, responseHandler);
} finally {
client.getConnectionManager().shutdown();
} }
} }
} }

@ -56,7 +56,11 @@
<!-- ROLE_USER --> <!-- ROLE_USER -->
<security:intercept-url pattern="/**" access="hasRole('ROLE_USER')" /> <security:intercept-url pattern="/**" access="hasRole('ROLE_USER')" />
<security:form-login login-page="/login.view" default-target-url="/home.view" authentication-failure-url="/login.view?error=1"/> <security:form-login login-page="/login.view"
default-target-url="/index.view"
authentication-failure-url="/login.view?error=1"
always-use-default-target="true"/>
<security:remember-me user-service-ref="securityService" key="libresonic"/>
</security:http> </security:http>
<security:authentication-manager> <security:authentication-manager>

@ -100,6 +100,7 @@ playlist.clear = Clear
playlist.shuffle = Shuffle playlist.shuffle = Shuffle
playlist.repeat_on = Repeat is on playlist.repeat_on = Repeat is on
playlist.repeat_off = Repeat is off playlist.repeat_off = Repeat is off
playlist.repeat_radio = Stop shuffle radio
playlist.undo = Undo playlist.undo = Undo
playlist.settings = Settings playlist.settings = Settings
playlist.more = More actions... playlist.more = More actions...
@ -233,7 +234,8 @@ more.random.text = Shuffle play
more.random.songs = {0} songs more.random.songs = {0} songs
more.random.auto = Play more random songs when end of play queue is reached. more.random.auto = Play more random songs when end of play queue is reached.
more.random.ok = OK 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.any = Any
more.random.format = Format more.random.format = Format
more.random.genre = Genre more.random.genre = Genre

@ -35,6 +35,10 @@
<tr> <tr>
<td align="left"><input name="submit" type="submit" value="<fmt:message key="login.login"/>" tabindex="4"></td> <td align="left"><input name="submit" type="submit" value="<fmt:message key="login.login"/>" tabindex="4"></td>
<td align="left" class="detail">
<input type="checkbox" name="_spring_security_remember_me" id="remember" class="checkbox" tabindex="3">
<label for="remember"><fmt:message key="login.remember"/></label>
</td>
</tr> </tr>
<tr> <tr>
<td></td> <td></td>

@ -232,24 +232,13 @@
<option value="mp3">MP3</option> <option value="mp3">MP3</option>
</select> </select>
</td> </td>
<td><fmt:message key="more.random.addtoplaylist"/></td>
<td>
<input name="addToPlaylist" value="true" type="checkbox"/>
</td>
</tr> </tr>
<tr> <tr>
<td colspan="2"> <td colspan="2">
<input type="submit" value="<fmt:message key="more.random.ok"/>"> <input type="submit" name="addToPlaylist" value="<fmt:message key="more.random.add"/>">
<input type="submit" name="autoRandom" value="<fmt:message key="more.random.radio"/>">
</td> </td>
</tr> </tr>
<c:if test="${not model.clientSidePlaylist}">
<tr>
<td colspan="9">
<input type="checkbox" name="autoRandom" id="autoRandom" class="checkbox"/>
<label for="autoRandom"><fmt:message key="more.random.auto"/></label>
</td>
</tr>
</c:if>
</table> </table>
</form> </form>
</c:if> </c:if>

@ -37,6 +37,7 @@
var songs = null; var songs = null;
var currentStreamUrl = null; var currentStreamUrl = null;
var repeatEnabled = false; var repeatEnabled = false;
var radioEnabled = false;
var isVisible = ${model.autoHide ? 'false' : 'true'}; var isVisible = ${model.autoHide ? 'false' : 'true'};
var CastPlayer = new CastPlayer(); var CastPlayer = new CastPlayer();
var ignore = false; var ignore = false;
@ -269,7 +270,13 @@
} }
function onNext(wrap) { function onNext(wrap) {
var index = parseInt(getCurrentSongIndex()) + 1; 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; index = index % songs.length;
} }
onSkip(index); onSkip(index);
@ -402,14 +409,20 @@
function playQueueCallback(playQueue) { function playQueueCallback(playQueue) {
songs = playQueue.entries; songs = playQueue.entries;
repeatEnabled = playQueue.repeatEnabled; repeatEnabled = playQueue.repeatEnabled;
radioEnabled = playQueue.radioEnabled;
if ($("#start")) { if ($("#start")) {
$("#start").toggle(!playQueue.stopEnabled); $("#start").toggle(!playQueue.stopEnabled);
$("#stop").toggle(playQueue.stopEnabled); $("#stop").toggle(playQueue.stopEnabled);
} }
if ($("#toggleRepeat")) { if ($("#toggleRepeat")) {
var text = repeatEnabled ? "<fmt:message key="playlist.repeat_on"/>" : "<fmt:message key="playlist.repeat_off"/>"; if (radioEnabled) {
$("#toggleRepeat").html(text); $("#toggleRepeat").html("<fmt:message key="playlist.repeat_radio"/>");
} else if (repeatEnabled) {
$("#toggleRepeat").html("<fmt:message key="playlist.repeat_on"/>");
} else {
$("#toggleRepeat").html("<fmt:message key="playlist.repeat_off"/>");
}
} }
if (songs.length == 0) { if (songs.length == 0) {

@ -8,7 +8,7 @@
<parent> <parent>
<groupId>org.libresonic.player</groupId> <groupId>org.libresonic.player</groupId>
<artifactId>libresonic</artifactId> <artifactId>libresonic</artifactId>
<version>6.2.spring4-SNAPSHOT</version> <version>6.2.beta1.spring4</version>
</parent> </parent>
<build> <build>

@ -8,7 +8,7 @@
<parent> <parent>
<groupId>org.libresonic.player</groupId> <groupId>org.libresonic.player</groupId>
<artifactId>libresonic</artifactId> <artifactId>libresonic</artifactId>
<version>6.2.spring4-SNAPSHOT</version> <version>6.2.beta1.spring4</version>
</parent> </parent>
<build> <build>

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.libresonic.player</groupId> <groupId>org.libresonic.player</groupId>
<artifactId>libresonic</artifactId> <artifactId>libresonic</artifactId>
<version>6.2.spring4-SNAPSHOT</version> <version>6.2.beta1.spring4</version>
<name>Libresonic</name> <name>Libresonic</name>
<packaging>pom</packaging> <packaging>pom</packaging>
<organization> <organization>

Loading…
Cancel
Save