aboutsummaryrefslogtreecommitdiff
path: root/subsonic-main/src/main/java/net/sourceforge/subsonic/service/NetworkService.java
diff options
context:
space:
mode:
Diffstat (limited to 'subsonic-main/src/main/java/net/sourceforge/subsonic/service/NetworkService.java')
-rw-r--r--subsonic-main/src/main/java/net/sourceforge/subsonic/service/NetworkService.java336
1 files changed, 336 insertions, 0 deletions
diff --git a/subsonic-main/src/main/java/net/sourceforge/subsonic/service/NetworkService.java b/subsonic-main/src/main/java/net/sourceforge/subsonic/service/NetworkService.java
new file mode 100644
index 00000000..b54026a0
--- /dev/null
+++ b/subsonic-main/src/main/java/net/sourceforge/subsonic/service/NetworkService.java
@@ -0,0 +1,336 @@
+/*
+ This file is part of Subsonic.
+
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2009 (C) Sindre Mehus
+ */
+package net.sourceforge.subsonic.service;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+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.entity.UrlEncodedFormEntity;
+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.message.BasicNameValuePair;
+import org.apache.http.params.HttpConnectionParams;
+import org.apache.http.util.EntityUtils;
+
+import net.sourceforge.subsonic.Logger;
+import net.sourceforge.subsonic.domain.NATPMPRouter;
+import net.sourceforge.subsonic.domain.Router;
+import net.sourceforge.subsonic.domain.SBBIRouter;
+import net.sourceforge.subsonic.domain.WeUPnPRouter;
+import net.sourceforge.subsonic.util.StringUtil;
+import net.sourceforge.subsonic.util.Util;
+
+/**
+ * Provides network-related services, including port forwarding on UPnP routers and
+ * URL redirection from http://xxxx.subsonic.org.
+ *
+ * @author Sindre Mehus
+ */
+public class NetworkService {
+
+ private static final Logger LOG = Logger.getLogger(NetworkService.class);
+ private static final long PORT_FORWARDING_DELAY = 3600L;
+ private static final long URL_REDIRECTION_DELAY = 2 * 3600L;
+
+ private static final String URL_REDIRECTION_REGISTER_URL = getBackendUrl() + "/backend/redirect/register.view";
+ private static final String URL_REDIRECTION_UNREGISTER_URL = getBackendUrl() + "/backend/redirect/unregister.view";
+ private static final String URL_REDIRECTION_TEST_URL = getBackendUrl() + "/backend/redirect/test.view";
+
+ private SettingsService settingsService;
+ private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(4);
+ private final PortForwardingTask portForwardingTask = new PortForwardingTask();
+ private final URLRedirectionTask urlRedirectionTask = new URLRedirectionTask();
+ private Future<?> portForwardingFuture;
+ private Future<?> urlRedirectionFuture;
+
+ private final Status portForwardingStatus = new Status();
+ private final Status urlRedirectionStatus = new Status();
+ private boolean testUrlRedirection;
+
+ public void init() {
+ initPortForwarding();
+ initUrlRedirection(false);
+ }
+
+ /**
+ * Configures UPnP port forwarding.
+ */
+ public synchronized void initPortForwarding() {
+ portForwardingStatus.setText("Idle");
+ if (portForwardingFuture != null) {
+ portForwardingFuture.cancel(true);
+ }
+ portForwardingFuture = executor.scheduleWithFixedDelay(portForwardingTask, 0L, PORT_FORWARDING_DELAY, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Configures URL redirection.
+ *
+ * @param test Whether to test that the redirection works.
+ */
+ public synchronized void initUrlRedirection(boolean test) {
+ urlRedirectionStatus.setText("Idle");
+ if (urlRedirectionFuture != null) {
+ urlRedirectionFuture.cancel(true);
+ }
+ testUrlRedirection = test;
+ urlRedirectionFuture = executor.scheduleWithFixedDelay(urlRedirectionTask, 0L, URL_REDIRECTION_DELAY, TimeUnit.SECONDS);
+ }
+
+ public Status getPortForwardingStatus() {
+ return portForwardingStatus;
+ }
+
+ public Status getURLRedirecionStatus() {
+ return urlRedirectionStatus;
+ }
+
+ public static String getBackendUrl() {
+ return "true".equals(System.getProperty("subsonic.test")) ? "http://localhost:8181" : "http://subsonic.org";
+ }
+
+ public void setSettingsService(SettingsService settingsService) {
+ this.settingsService = settingsService;
+ }
+
+ private class PortForwardingTask extends Task {
+
+ @Override
+ protected void execute() {
+
+ boolean enabled = settingsService.isPortForwardingEnabled();
+ portForwardingStatus.setText("Looking for router...");
+ Router router = findRouter();
+ if (router == null) {
+ LOG.warn("No UPnP router found.");
+ portForwardingStatus.setText("No router found.");
+ } else {
+
+ portForwardingStatus.setText("Router found.");
+
+ int port = settingsService.getPort();
+ int httpsPort = settingsService.getHttpsPort();
+
+ // Create new NAT entry.
+ if (enabled) {
+ try {
+ router.addPortMapping(port, port, 0);
+ String message = "Successfully forwarding port " + port;
+
+ if (httpsPort != 0 && httpsPort != port) {
+ router.addPortMapping(httpsPort, httpsPort, 0);
+ message += " and port " + httpsPort;
+ }
+ message += ".";
+
+ LOG.info(message);
+ portForwardingStatus.setText(message);
+ } catch (Throwable x) {
+ String message = "Failed to create port forwarding.";
+ LOG.warn(message, x);
+ portForwardingStatus.setText(message + " See log for details.");
+ }
+ }
+
+ // Delete NAT entry.
+ else {
+ try {
+ router.deletePortMapping(port, port);
+ LOG.info("Deleted port mapping for port " + port);
+ if (httpsPort != 0 && httpsPort != port) {
+ router.deletePortMapping(httpsPort, httpsPort);
+ LOG.info("Deleted port mapping for port " + httpsPort);
+ }
+ } catch (Throwable x) {
+ LOG.warn("Failed to delete port mapping.", x);
+ }
+ portForwardingStatus.setText("Port forwarding disabled.");
+ }
+ }
+
+ // Don't do it again if disabled.
+ if (!enabled && portForwardingFuture != null) {
+ portForwardingFuture.cancel(false);
+ }
+ }
+
+ private Router findRouter() {
+ try {
+ Router router = SBBIRouter.findRouter();
+ if (router != null) {
+ return router;
+ }
+ } catch (Throwable x) {
+ LOG.warn("Failed to find UPnP router using SBBI library.", x);
+ }
+
+ try {
+ Router router = WeUPnPRouter.findRouter();
+ if (router != null) {
+ return router;
+ }
+ } catch (Throwable x) {
+ LOG.warn("Failed to find UPnP router using WeUPnP library.", x);
+ }
+
+ try {
+ Router router = NATPMPRouter.findRouter();
+ if (router != null) {
+ return router;
+ }
+ } catch (Throwable x) {
+ LOG.warn("Failed to find NAT-PMP router.", x);
+ }
+
+ return null;
+ }
+ }
+
+ private class URLRedirectionTask extends Task {
+
+ @Override
+ protected void execute() {
+
+ boolean enable = settingsService.isUrlRedirectionEnabled();
+ HttpPost request = new HttpPost(enable ? URL_REDIRECTION_REGISTER_URL : URL_REDIRECTION_UNREGISTER_URL);
+
+ int port = settingsService.getPort();
+ boolean trial = !settingsService.isLicenseValid();
+ Date trialExpires = settingsService.getUrlRedirectTrialExpires();
+
+ List<NameValuePair> params = new ArrayList<NameValuePair>();
+ params.add(new BasicNameValuePair("serverId", settingsService.getServerId()));
+ params.add(new BasicNameValuePair("redirectFrom", settingsService.getUrlRedirectFrom()));
+ params.add(new BasicNameValuePair("port", String.valueOf(port)));
+ params.add(new BasicNameValuePair("localIp", Util.getLocalIpAddress()));
+ params.add(new BasicNameValuePair("localPort", String.valueOf(port)));
+ params.add(new BasicNameValuePair("contextPath", settingsService.getUrlRedirectContextPath()));
+ params.add(new BasicNameValuePair("trial", String.valueOf(trial)));
+ if (trial && trialExpires != null) {
+ params.add(new BasicNameValuePair("trialExpires", String.valueOf(trialExpires.getTime())));
+ } else {
+ params.add(new BasicNameValuePair("licenseHolder", settingsService.getLicenseEmail()));
+ }
+
+ HttpClient client = new DefaultHttpClient();
+
+ try {
+ 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()));
+ break;
+ case HttpStatus.SC_OK:
+ urlRedirectionStatus.setText(enable ? "Successfully registered web address." : "Web address disabled.");
+ break;
+ default:
+ 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.
+ if (testUrlRedirection) {
+ testUrlRedirection = false;
+ testUrlRedirection();
+ }
+
+ // Don't do it again if disabled.
+ if (!enable && urlRedirectionFuture != null) {
+ urlRedirectionFuture.cancel(false);
+ }
+ }
+
+ private void testUrlRedirection() {
+
+ HttpGet request = new HttpGet(URL_REDIRECTION_TEST_URL + "?redirectFrom=" + settingsService.getUrlRedirectFrom());
+ HttpClient client = new DefaultHttpClient();
+ HttpConnectionParams.setConnectionTimeout(client.getParams(), 10000);
+ HttpConnectionParams.setSoTimeout(client.getParams(), 30000);
+
+ try {
+ urlRedirectionStatus.setText("Testing web address " + settingsService.getUrlRedirectFrom() + ".subsonic.org. 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();
+ }
+ }
+ }
+
+ private abstract class Task implements Runnable {
+ public void run() {
+ String name = getClass().getSimpleName();
+ try {
+ execute();
+ } catch (Throwable x) {
+ LOG.error("Error executing " + name + ": " + x.getMessage(), x);
+ }
+ }
+
+ protected abstract void execute();
+ }
+
+ public static class Status {
+
+ private String text;
+ private Date date;
+
+ public void setText(String text) {
+ this.text = text;
+ date = new Date();
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public Date getDate() {
+ return date;
+ }
+ }
+}