/* * Copyright 2004 - 2008 Christian Sprajc. All rights reserved. * * This file is part of PowerFolder. * * PowerFolder 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. * * PowerFolder 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 PowerFolder. If not, see <http://www.gnu.org/licenses/>. * * $Id$ */ package de.dal33t.powerfolder.ui.util; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.SocketTimeoutException; import java.net.URL; import java.net.URLConnection; import java.net.UnknownHostException; import java.nio.CharBuffer; import java.util.logging.Level; import java.util.logging.Logger; import de.dal33t.powerfolder.ConfigurationEntry; import de.dal33t.powerfolder.Constants; import de.dal33t.powerfolder.Controller; import de.dal33t.powerfolder.PreferencesEntry; import de.dal33t.powerfolder.event.NetworkingModeListener; import de.dal33t.powerfolder.event.NetworkingModeEvent; import de.dal33t.powerfolder.light.MemberInfo; import de.dal33t.powerfolder.message.KnownNodes; import de.dal33t.powerfolder.message.KnownNodesExt; import de.dal33t.powerfolder.message.Message; import de.dal33t.powerfolder.message.SingleMessageProducer; import de.dal33t.powerfolder.ui.WikiLinks; import de.dal33t.powerfolder.ui.dialog.DialogFactory; import de.dal33t.powerfolder.ui.dialog.GenericDialogType; import de.dal33t.powerfolder.util.Reject; import de.dal33t.powerfolder.util.StringUtils; import de.dal33t.powerfolder.util.Translation; /** * Checks the connectivity if run and opens a dialog when UI is open. * * @author <a href="mailto:totmacher@powerfolder.com">Christian Sprajc</a> * @version $Revision: 1.5 $ */ public class LimitedConnectivityChecker { private static final Logger log = Logger .getLogger(LimitedConnectivityChecker.class.getName()); private static final String LIMITED_CONNECTIVITY_TEST_SUCCESSFULLY_STRING = "LIMITED CONNECTIVITY TEST SUCCESSFULLY"; private Controller controller; private String host; private int port; public LimitedConnectivityChecker(Controller controller) { Reject.ifNull(controller, "Controller is null"); this.controller = controller; } /** * Central method to check if the connectivity is limited. Method call may * take some time. * * @return true the connectivty is limited. */ public boolean hasLimitedConnecvitiy() { if (!controller.getNodeManager().getMySelf().isSupernode()) { if (controller.getOSClient().isConnected()) { log .fine("No limited connectivity. Connected to the Online Storage"); return false; } if (controller.getIOProvider().getRelayedConnectionManager() .getRelay() != null) { log.fine("No limited connectivity. Connected to a relay"); return false; } // If not, try the full incoming connection check. } // Be more restrictive on supernode. Needs incoming connections from // internet. or clients without a connected webservice. if (!resolveHostAndPort()) { log.warning("Unable resolve own host"); return true; } // Try three times, just to make sure we don't hit a full backlog boolean connectOK = isConnectPossible() || isConnectPossible() || isConnectPossible(); return !connectOK; } /** * Installs a task to check the connectivity of this system once. or when * the networking mode changes. * * @param ctrl */ public static void install(Controller ctrl) { Reject.ifNull(ctrl, "Controller is null"); CheckTask task = new CheckTask(ctrl); ctrl.schedule(task, 1000L * Constants.LIMITED_CONNECTIVITY_CHECK_DELAY); // Support networking mode switch. ctrl.addNetworkingModeListener(new MyNetworkingModeListener(ctrl)); } public String getHost() { return host; } public int getPort() { return port; } // Helper class *********************************************************** public static class CheckTask implements Runnable { private Controller controller; public CheckTask(Controller controller) { this.controller = controller; } public void run() { if (controller.isLanOnly()) { // No limited connectivity in lan only mode. controller.setLimitedConnectivity(false); return; } if (!controller.getNodeManager().isStarted()) { // Skip check if de-activated. return; } if (!PreferencesEntry.WARN_ON_NO_DIRECT_CONNECTIVITY.getValueBoolean(controller)) { return; } LimitedConnectivityChecker checker = new LimitedConnectivityChecker( controller); log.fine("Checking for limited connectivity (" + checker.getHost() + ':' + checker.getPort() + ')'); // boolean wasLimited = controller.isLimitedConnectivity(); boolean nowLimited = checker.hasLimitedConnecvitiy(); controller.setLimitedConnectivity(nowLimited); if (nowLimited) { log.warning("Unable to connect from the Internet to " + checker.getHost() + ':' + checker.getPort()); } else { log.info("Connectivity is good"); } setSupernodeState(nowLimited); } private void setSupernodeState(boolean limitedCon) { boolean dyndnsSetup = controller.getConnectionListener() .getMyDynDns() != null; if (!dyndnsSetup) { return; } final MemberInfo me = controller.getMySelf().getInfo(); me.isSupernode = !limitedCon; if (!controller.getMySelf().getInfo().isSupernode) { return; } log .fine("Acting as supernode on address " + me.getConnectAddress()); // Broadcast our new status, we want stats ;) controller.getNodeManager().broadcastMessage(107, new SingleMessageProducer() { @Override public Message getMessage(boolean useExt) { return useExt ? new KnownNodesExt(me) : new KnownNodes( me); } }, null); } } // Internal logic ********************************************************* private boolean resolveHostAndPort() { String dyndns = ConfigurationEntry.HOSTNAME.getValue(controller); boolean hasDyndnsSetup = !StringUtils.isEmpty(dyndns); port = controller.getConnectionListener().getPort(); // 1.1) Setup with dyndns = best inet setup if (hasDyndnsSetup) { try { InetAddress.getAllByName(dyndns); host = dyndns; } catch (UnknownHostException e) { // Dyndns host could not be resolved host = null; } } // 1.2) Setup with provider ip = moderate inet setup if (StringUtils.isEmpty(host)) { host = controller.getDynDnsManager().getIPviaHTTPCheckIP(); } log.finer("Will check connectivity on " + host + ':' + port); return !StringUtils.isEmpty(host); } private boolean isConnectPossible() { Reject.ifBlank(host, "Hostname or port resolved, resolve it first"); Reject.ifTrue(port <= 0, "Hostname or port resolved, resolve it first"); URL url; try { url = new URL(Constants.LIMITED_CONNECTIVITY_CHECK_URL + "?host=" + host + "&port=" + port); } catch (MalformedURLException e) { log.log(Level.WARNING, "Limited connectivity check failed for " + host + ':' + port, e); return false; } InputStream in = null; try { URLConnection con = url.openConnection(); con.setConnectTimeout(30 * 1000); con.setReadTimeout(30 * 1000); con.connect(); in = con.getInputStream(); Reader reader = new InputStreamReader(new BufferedInputStream(in)); CharBuffer buf = CharBuffer.allocate(in.available()); reader.read(buf); reader.close(); String testString = new String(buf.array()); return testString .contains(LIMITED_CONNECTIVITY_TEST_SUCCESSFULLY_STRING); } catch (SocketTimeoutException e) { log.log(Level.WARNING, "Limited connectivity check failed for " + host + ':' + port + ". " + e); log.log(Level.FINER, "SocketTimeoutException", e); return false; } catch (IOException e) { log.log(Level.WARNING, "Limited connectivity check failed for " + host + ':' + port + ". " + e); return false; } finally { if (in != null) { try { in.close(); } catch (IOException e) { log.log(Level.FINER, "IOException", e); } } } } public static void showConnectivityWarning(final Controller controllerArg) { Runnable showMessage = new Runnable() { public void run() { String wikiLink = Help.getWikiArticleURL(controllerArg, WikiLinks.LIMITED_CONNECTIVITY); if (StringUtils.isBlank(wikiLink)) { wikiLink = ""; } NeverAskAgainResponse response = DialogFactory.genericDialog( controllerArg, Translation .getTranslation("limited_connection.title"), Translation.getTranslation("limited_connection.text", wikiLink), new String[]{Translation .getTranslation("general.ok")}, 0, GenericDialogType.INFO, Translation .getTranslation("limited_connection.dont_autodetect")); if (response.isNeverAskAgain()) { PreferencesEntry.WARN_ON_NO_DIRECT_CONNECTIVITY.setValue(controllerArg, false); log.warning("store do not show this dialog again"); } } }; controllerArg.getUIController().invokeLater(showMessage); } private static class MyNetworkingModeListener implements NetworkingModeListener { private Controller controller; private MyNetworkingModeListener(Controller controller) { this.controller = controller; } public void setNetworkingMode(NetworkingModeEvent event) { controller.getThreadPool().execute(new CheckTask(controller)); } public boolean fireInEventDispatchThread() { return true; } } }