/***************************************************************************
* Copyright 2006-2016 by Christian Ihle *
* contact@kouchat.net *
* *
* This file is part of KouChat. *
* *
* KouChat is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 3 of *
* the License, or (at your option) any later version. *
* *
* KouChat 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 *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with KouChat. *
* If not, see <http://www.gnu.org/licenses/>. *
***************************************************************************/
package net.usikkert.kouchat.net;
import java.net.NetworkInterface;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.usikkert.kouchat.event.NetworkConnectionListener;
import net.usikkert.kouchat.misc.ErrorHandler;
import net.usikkert.kouchat.settings.Settings;
import net.usikkert.kouchat.util.Validate;
/**
* This thread is responsible for keeping the application connected
* to the network.
*
* Every now and then, the thread will check if there are better
* networks available, and reconnect to that network instead.
*
* @author Christian Ihle
*/
public class ConnectionWorker implements Runnable {
/** The logger. */
private static final Logger LOG = Logger.getLogger(ConnectionWorker.class.getName());
/** Period of time to sleep if network is up. 60 sec. */
private static final int SLEEP_UP = 1000 * 60;
/** Period of time to sleep if network is down. 15 sec. */
private static final int SLEEP_DOWN = 1000 * 15;
private final NetworkUtils networkUtils = new NetworkUtils();
/** Indicates whether the thread should run or not. */
private boolean run;
/** Whether the network is up or not. */
private boolean networkUp;
/** The current network interface. */
private NetworkInterface networkInterface;
/** The working thread. */
private Thread worker;
/** A list of connection listeners. */
private final List<NetworkConnectionListener> listeners;
/** For locating the operating system's choice of network interface. */
private final OperatingSystemNetworkInfo osNetworkInfo;
/** The settings to use for the network. */
private final Settings settings;
/**
* Constructor.
*
* @param settings The settings to use.
* @param errorHandler The error handler to use.
*/
public ConnectionWorker(final Settings settings, final ErrorHandler errorHandler) {
Validate.notNull(settings, "Settings can not be null");
Validate.notNull(errorHandler, "Error handler can not be null");
this.settings = settings;
listeners = new ArrayList<NetworkConnectionListener>();
osNetworkInfo = new OperatingSystemNetworkInfo(settings, errorHandler);
}
/**
* The thread. See {@link #updateNetwork()} for details.
*/
@Override
public void run() {
LOG.log(Level.FINE, "Network is starting");
while (run) {
final boolean networkUp = updateNetwork();
if (!run) {
break;
}
try {
if (networkUp) {
Thread.sleep(SLEEP_UP);
} else {
Thread.sleep(SLEEP_DOWN);
}
}
// Sleep interrupted - probably from stop() or checkNetwork()
catch (final InterruptedException e) {
LOG.log(Level.FINE, e.toString());
}
}
LOG.log(Level.FINE, "Network is stopping");
if (networkUp) {
notifyNetworkDown(false);
}
networkInterface = null;
}
/**
* Asks the thread to check the network now to detect loss of network connectivity.
*/
public void checkNetwork() {
if (worker != null) {
worker.interrupt();
}
}
/**
* Checks the state of the network, and tries to keep the best possible
* network connection up. Listeners are notified of any changes.
*
* @return If the network is up or not after this update is done.
*/
private synchronized boolean updateNetwork() {
final NetworkInterface netif = selectNetworkInterface();
// No network interface to connect with
if (!networkUtils.isUsable(netif)) {
LOG.log(Level.FINE, "Network is down");
if (networkUp) {
notifyNetworkDown(false);
}
return false;
}
// Switching network interface, like going from cable to wireless
else if (isNewNetworkInterface(netif)) {
final String origNetwork = networkInterface == null ? "[null]" : networkInterface.getName();
LOG.log(Level.FINE, "Changing network from " + origNetwork + " to " + netif.getName());
networkInterface = netif;
if (networkUp) {
notifyNetworkDown(true);
notifyNetworkUp(true);
}
else {
notifyNetworkUp(false);
}
}
// If the connection was lost, like unplugging cable, and plugging back in
else if (!networkUp) {
LOG.log(Level.FINE, "Network " + netif.getName() + " is up again");
networkInterface = netif;
notifyNetworkUp(false);
}
// Else, the old connection is still up
return true;
}
/**
* Compares <code>netif</code> with the current network interface.
*
* @param netif The new network interface to compare against the original.
* @return True if netif is new.
*/
private boolean isNewNetworkInterface(final NetworkInterface netif) {
return !networkUtils.sameNetworkInterface(netif, networkInterface);
}
/**
* Notifies all the listeners that they can prepare to be notified that the network is up.
*/
private synchronized void notifyBeforeNetworkUp() {
for (final NetworkConnectionListener listener : listeners) {
listener.beforeNetworkCameUp();
}
}
/**
* Notifies all the listeners that the network is up.
*
* @param silent Don't give any messages to the user about the change.
*/
private synchronized void notifyNetworkUp(final boolean silent) {
notifyBeforeNetworkUp();
networkUp = true;
for (final NetworkConnectionListener listener : listeners) {
listener.networkCameUp(silent);
}
}
/**
* Notifies all the listeners that the network is down.
*
* @param silent Don't give any messages to the user about the change.
*/
private synchronized void notifyNetworkDown(final boolean silent) {
networkUp = false;
for (final NetworkConnectionListener listener : listeners) {
listener.networkWentDown(silent);
}
}
/**
* Registers the listener as a connection listener.
*
* @param listener The listener to register.
*/
public void registerNetworkConnectionListener(final NetworkConnectionListener listener) {
listeners.add(listener);
}
/**
* Starts a new thread if no thread is already running.
*/
public synchronized void start() {
if (!run && !isAlive()) {
run = true;
worker = new Thread(this, "ConnectionWorker");
worker.start();
}
}
/**
* Stops the thread.
*/
public void stop() {
run = false;
if (worker != null) {
worker.interrupt();
}
}
/**
* Locates the best network interface to use.
*
* <ol>
* <li>The first priority is the network interface selected in the settings.</li>
* <li>The second priority is the operating system's choice of network interface.</li>
* <li>The last priority is KouChat's own choice of network interface.</li>
* </ol>
*
* <p>If no usable network interfaces are found, then <code>null</code>
* is returned.</p>
*
* @return The network interface found, or <code>null</code>.
* @see NetworkUtils#isUsable(NetworkInterface)
*/
private NetworkInterface selectNetworkInterface() {
final NetworkInterface firstUsableNetIf = networkUtils.findFirstUsableNetworkInterface();
if (firstUsableNetIf == null) {
LOG.log(Level.FINER, "No usable network interface detected.");
return null;
}
final NetworkInterface savedNetworkInterface =
networkUtils.getNetworkInterfaceByName(settings.getNetworkInterface());
if (networkUtils.isUsable(savedNetworkInterface)) {
LOG.log(Level.FINER, "Using saved network interface: \n" +
networkUtils.getNetworkInterfaceInfo(savedNetworkInterface));
return savedNetworkInterface;
}
LOG.log(Level.FINER, "Saved network interface '" + settings.getNetworkInterface() + "' is invalid: \n" +
networkUtils.getNetworkInterfaceInfo(savedNetworkInterface));
final NetworkInterface osNetIf = osNetworkInfo.getOperatingSystemNetworkInterface();
if (networkUtils.isUsable(osNetIf)) {
LOG.log(Level.FINER, "Using operating system's choice of network interface: \n" +
networkUtils.getNetworkInterfaceInfo(osNetIf));
return osNetIf;
}
LOG.finer("The operating system suggested the following invalid network interface: \n" +
networkUtils.getNetworkInterfaceInfo(osNetIf));
LOG.log(Level.FINER, "Overriding operating system's choice of network interface with: \n" +
networkUtils.getNetworkInterfaceInfo(firstUsableNetIf));
return firstUsableNetIf;
}
/**
* Finds the current network interface.
*
* @return The current network interface.
*/
public NetworkInterface getCurrentNetworkInterface() {
final NetworkInterface updatedNetworkInterface =
networkUtils.getUpdatedNetworkInterface(networkInterface);
if (updatedNetworkInterface != null) {
return updatedNetworkInterface;
}
return networkInterface;
}
/**
* Checks if the network is up.
*
* @return If the network is up.
*/
public boolean isNetworkUp() {
return networkUp;
}
/**
* Checks if the thread is alive.
*
* @return If the thread is alive.
*/
public boolean isAlive() {
if (worker == null) {
return false;
} else {
return worker.isAlive();
}
}
}