diff options
author | Damian Minkov <damencho@jitsi.org> | 2010-05-17 07:21:11 +0000 |
---|---|---|
committer | Damian Minkov <damencho@jitsi.org> | 2010-05-17 07:21:11 +0000 |
commit | 3553f9ad0eacdb4e3d7befaaff49cdd8f0cdb35b (patch) | |
tree | 28732878b0184c812e01f12313e6fff75468c3b2 /src | |
parent | fd5c3ed8b6840eae1b14341b85db613226733178 (diff) | |
download | jitsi-3553f9ad0eacdb4e3d7befaaff49cdd8f0cdb35b.zip jitsi-3553f9ad0eacdb4e3d7befaaff49cdd8f0cdb35b.tar.gz jitsi-3553f9ad0eacdb4e3d7befaaff49cdd8f0cdb35b.tar.bz2 |
Adds and activates reconnect plugin. Some fixes to protocols in order to work with the plungin (removing all tries to reconnect as they are now controlled from outside).
Diffstat (limited to 'src')
15 files changed, 1248 insertions, 43 deletions
diff --git a/src/net/java/sip/communicator/impl/gui/main/login/LoginManager.java b/src/net/java/sip/communicator/impl/gui/main/login/LoginManager.java index 78a199c..412a91e 100644 --- a/src/net/java/sip/communicator/impl/gui/main/login/LoginManager.java +++ b/src/net/java/sip/communicator/impl/gui/main/login/LoginManager.java @@ -190,28 +190,29 @@ public class LoginManager logger.trace(evt.getReason()); } - else if (newState.equals(RegistrationState.CONNECTION_FAILED)) - { - String msgText = GuiActivator.getResources().getI18NString( - "service.gui.CONNECTION_FAILED_MSG", - new String[] - { accountID.getUserID(), - accountID.getService() }); - - int result = new MessageDialog( - null, - GuiActivator.getResources().getI18NString("service.gui.ERROR"), - msgText, - GuiActivator.getResources().getI18NString("service.gui.RETRY"), - false).showDialog(); - - if (result == MessageDialog.OK_RETURN_CODE) - { - this.login(protocolProvider); - } - - logger.trace(evt.getReason()); - } +// CONNECTION_FAILED events are now dispatched in reconnect plugin +// else if (newState.equals(RegistrationState.CONNECTION_FAILED)) +// { +// String msgText = GuiActivator.getResources().getI18NString( +// "service.gui.CONNECTION_FAILED_MSG", +// new String[] +// { accountID.getUserID(), +// accountID.getService() }); +// +// int result = new MessageDialog( +// null, +// GuiActivator.getResources().getI18NString("service.gui.ERROR"), +// msgText, +// GuiActivator.getResources().getI18NString("service.gui.RETRY"), +// false).showDialog(); +// +// if (result == MessageDialog.OK_RETURN_CODE) +// { +// this.login(protocolProvider); +// } +// +// logger.trace(evt.getReason()); +// } else if (newState.equals(RegistrationState.EXPIRED)) { String msgText = GuiActivator.getResources().getI18NString( diff --git a/src/net/java/sip/communicator/impl/netaddr/NetworkAddressManagerServiceImpl.java b/src/net/java/sip/communicator/impl/netaddr/NetworkAddressManagerServiceImpl.java index 9b67a47..79f9dd9 100644 --- a/src/net/java/sip/communicator/impl/netaddr/NetworkAddressManagerServiceImpl.java +++ b/src/net/java/sip/communicator/impl/netaddr/NetworkAddressManagerServiceImpl.java @@ -13,6 +13,7 @@ import java.beans.*; import net.java.sip.communicator.service.configuration.*; import net.java.sip.communicator.service.netaddr.*; +import net.java.sip.communicator.service.netaddr.event.*; import net.java.sip.communicator.util.*; import net.java.stun4j.*; import net.java.stun4j.client.*; @@ -115,6 +116,11 @@ public class NetworkAddressManagerServiceImpl */ public static final int DEFAULT_STUN_SERVER_PORT = 3478; + /** + * A thread which periodically scans network interfaces and reports + * changes in network configuration. + */ + private NetworkConfigurationWatcher networkConfigurationWatcher = null; /** * Initializes this network address manager service implementation and @@ -221,6 +227,8 @@ public class NetworkAddressManagerServiceImpl configurationService .removeVetoableChangeListener( PROP_STUN_SERVER_PORT, this); + if(networkConfigurationWatcher != null) + networkConfigurationWatcher.stop(); } finally { @@ -808,5 +816,30 @@ public class NetworkAddressManagerServiceImpl + minPort + " and " + (port -1)); } + /** + * Adds new <tt>NetworkConfigurationChangeListener</tt> which will + * be informed for network configuration changes. + * @param listener the listener. + */ + public void addNetworkConfigurationChangeListener( + NetworkConfigurationChangeListener listener) + { + if(networkConfigurationWatcher == null) + networkConfigurationWatcher = new NetworkConfigurationWatcher(); + networkConfigurationWatcher + .addNetworkConfigurationChangeListener(listener); + } + + /** + * Remove <tt>NetworkConfigurationChangeListener</tt>. + * @param listener the listener. + */ + public void removeNetworkConfigurationChangeListener( + NetworkConfigurationChangeListener listener) + { + if(networkConfigurationWatcher != null) + networkConfigurationWatcher + .removeNetworkConfigurationChangeListener(listener); + } } diff --git a/src/net/java/sip/communicator/impl/netaddr/NetworkConfigurationWatcher.java b/src/net/java/sip/communicator/impl/netaddr/NetworkConfigurationWatcher.java new file mode 100644 index 0000000..d071fc8 --- /dev/null +++ b/src/net/java/sip/communicator/impl/netaddr/NetworkConfigurationWatcher.java @@ -0,0 +1,313 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.netaddr; + +import java.lang.reflect.*; +import java.net.*; +import java.util.*; +import net.java.sip.communicator.service.netaddr.event.*; +import net.java.sip.communicator.util.*; + +/** + * Periodically checks the current network interfaces to track changes + * and fire events on those changes. + * + * @author Damian Minkov + */ +public class NetworkConfigurationWatcher + implements Runnable +{ + /** + * Our class logger. + */ + private static Logger logger = + Logger.getLogger(NetworkConfigurationWatcher.class); + + /** + * Interval between check of network configuration. + */ + private static final int CHECK_INTERVAL = 3000; // 3 sec. + + /** + * Listers for network configuration changes. + */ + private final List<NetworkConfigurationChangeListener> listeners = + new ArrayList<NetworkConfigurationChangeListener>(); + + /** + * Whether current thread is running. + */ + private boolean isRunning = false; + + /** + * Adds new <tt>NetworkConfigurationChangeListener</tt> which will + * be informed for network configuration changes. + * @param listener the listener. + */ + void addNetworkConfigurationChangeListener( + NetworkConfigurationChangeListener listener) + { + synchronized(listeners) + { + listeners.add(listener); + } + + if(!isRunning) + { + isRunning = true; + new Thread(this).start(); + } + } + + /** + * Remove <tt>NetworkConfigurationChangeListener</tt>. + * @param listener the listener. + */ + void removeNetworkConfigurationChangeListener( + NetworkConfigurationChangeListener listener) + { + synchronized(listeners) + { + listeners.remove(listener); + } + } + + /** + * Main loop of this thread. + */ + public void run() + { + long last = 0; + boolean isAfterStandby = false; + + List<NetworkInterface> activeInterfaces = + new ArrayList<NetworkInterface>(); + + while(isRunning) + { + long curr = System.currentTimeMillis(); + + if(!isAfterStandby && last != 0) + isAfterStandby = (last + CHECK_INTERVAL + 100 - curr) < 0; + + try + { + Enumeration<NetworkInterface> e = + NetworkInterface.getNetworkInterfaces(); + + boolean networkIsUP = activeInterfaces.size() > 0; + + List<NetworkInterface> currentActiveInterfaces = + new ArrayList<NetworkInterface>(); + + while (e.hasMoreElements()) + { + NetworkInterface networkInterface = e.nextElement(); + + if(isInterfaceLoopback(networkInterface)) + continue; + + // if interface is up and has some valid(non-local) address + if(isInterfaceUp(networkInterface) + && hasValidAddress(networkInterface)) + { + currentActiveInterfaces.add(networkInterface); + } + } + + List<NetworkInterface> inactiveActiveInterfaces = + new ArrayList<NetworkInterface>(activeInterfaces); + inactiveActiveInterfaces.removeAll(currentActiveInterfaces); + + // fire that interface has gone down + for (int i = 0; i < inactiveActiveInterfaces.size(); i++) + { + NetworkInterface iface = inactiveActiveInterfaces.get(i); + + if(!containsInterfaceWithName( + currentActiveInterfaces, iface.getName())) + { + fireChangeEvent(new ChangeEvent(iface, + ChangeEvent.IFACE_DOWN, isAfterStandby)); + + activeInterfaces.remove(iface); + } + } + + // now we leave with only with the new and up interfaces + // in currentActiveInterfaces list + currentActiveInterfaces.removeAll(activeInterfaces); + + // fire that interface has gone up + for (int i = 0; i < currentActiveInterfaces.size(); i++) + { + NetworkInterface iface = currentActiveInterfaces.get(i); + + fireChangeEvent(new ChangeEvent(iface, + ChangeEvent.IFACE_UP, isAfterStandby)); + activeInterfaces.add(iface); + } + + // fire that network has gone up + if(!networkIsUP && activeInterfaces.size() > 0) + { + isAfterStandby = false; + } + + last = curr; + } catch (SocketException e) + { + logger.error("Error checking network interfaces", e); + } + + synchronized(this) + { + try{ + wait(CHECK_INTERVAL); + } + catch (Exception e){} + } + } + } + + /** + * Fire ChangeEvent. + * @param evt the event to fire. + */ + private void fireChangeEvent(ChangeEvent evt) + { + synchronized(listeners) + { + for (int i = 0; i < listeners.size(); i++) + { + NetworkConfigurationChangeListener nCChangeListener + = listeners.get(i); + try + { + nCChangeListener.configurationChanged(evt); + } catch (Throwable e) + { + logger.warn("Error delivering event:" + evt + ", to:" + + nCChangeListener); + } + } + } + } + + + /** + * Stop current running thread. + */ + void stop() + { + synchronized(this) + { + isRunning = false; + notifyAll(); + } + } + + /** + * Checks whether the supplied network interface name is in the list. + * @param ifaces the list of interfaces. + * @param name the name to check. + * @return whether name is found in the list of interfaces. + */ + private boolean containsInterfaceWithName( + List<NetworkInterface> ifaces, String name) + { + for (int i = 0; i < ifaces.size(); i++) + { + NetworkInterface networkInterface = ifaces.get(i); + if(networkInterface.getName().equals(name)) + return true; + } + + return false; + } + + /** + * Whether the supplied interface has a valid non-local address. + * @param iface interface. + * @return has a valid address. + */ + private static boolean hasValidAddress(NetworkInterface iface) + { + Enumeration<InetAddress> as = + iface.getInetAddresses(); + while (as.hasMoreElements()) + { + InetAddress inetAddress = as.nextElement(); + if(inetAddress.isLinkLocalAddress()) + continue; + + return true; + } + + return false; + } + + /** + * Determines whether or not the <tt>iface</tt> interface is a loopback + * interface. We use this method as a replacement to the + * <tt>NetworkInterface.isLoopback()</tt> method that only comes with + * java 1.6. + * + * @param iface the interface that we'd like to determine as loopback or not. + * + * @return true if <tt>iface</tt> contains at least one loopback address + * and <tt>false</tt> otherwise. + */ + public static boolean isInterfaceLoopback(NetworkInterface iface) + { + try + { + Method method = iface.getClass().getMethod("isLoopback"); + + return ((Boolean)method.invoke(iface, new Object[]{})) + .booleanValue(); + } + catch(Throwable t) + { + //apparently we are not running in a JVM that supports the + //is Loopback method. we'll try another approach. + } + Enumeration<InetAddress> addresses = iface.getInetAddresses(); + + return addresses.hasMoreElements() + && addresses.nextElement().isLoopbackAddress(); + } + + /** + * Determines, if possible, whether or not the <tt>iface</tt> interface is + * up. We use this method so that we could use {@link + * java.net.NetworkInterface}'s <tt>isUp()</tt> when running a JVM that + * supports it and return a default value otherwise. + * + * @param iface the interface that we'd like to determine as Up or Down. + * + * @return <tt>false</tt> if <tt>iface</tt> is known to be down and + * <tt>true</tt> if the <tt>iface</tt> is Up or in case we couldn't + * determine. + */ + public static boolean isInterfaceUp(NetworkInterface iface) + { + try + { + Method method = iface.getClass().getMethod("isUp"); + + return ((Boolean)method.invoke(iface)).booleanValue(); + } + catch(Throwable t) + { + //apparently we are not running in a JVM that supports the + //isUp method. returning default value. + } + + return true; + } +} diff --git a/src/net/java/sip/communicator/impl/netaddr/netaddr.manifest.mf b/src/net/java/sip/communicator/impl/netaddr/netaddr.manifest.mf index af7d319..00eee79 100644 --- a/src/net/java/sip/communicator/impl/netaddr/netaddr.manifest.mf +++ b/src/net/java/sip/communicator/impl/netaddr/netaddr.manifest.mf @@ -9,3 +9,4 @@ Import-Package: net.java.sip.communicator.service.configuration, net.java.sip.communicator.util, org.osgi.framework, Export-Package: net.java.sip.communicator.service.netaddr, + net.java.sip.communicator.service.netaddr.event diff --git a/src/net/java/sip/communicator/impl/protocol/icq/ProtocolProviderServiceIcqImpl.java b/src/net/java/sip/communicator/impl/protocol/icq/ProtocolProviderServiceIcqImpl.java index afb1647..3ebcaee 100644 --- a/src/net/java/sip/communicator/impl/protocol/icq/ProtocolProviderServiceIcqImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/icq/ProtocolProviderServiceIcqImpl.java @@ -605,14 +605,6 @@ public class ProtocolProviderServiceIcqImpl int reasonCode, String reason) { - if(newState.equals(RegistrationState.CONNECTION_FAILED) && - isRegistered()) - { - // if for some reason (keep alive failed) and connection is - // still connected disconneted - unregister(); - } - lastRegistrationState = newState; super.fireRegistrationStateChanged( diff --git a/src/net/java/sip/communicator/impl/protocol/msn/EventManager.java b/src/net/java/sip/communicator/impl/protocol/msn/EventManager.java index 0ddce75..ad1f02d 100644 --- a/src/net/java/sip/communicator/impl/protocol/msn/EventManager.java +++ b/src/net/java/sip/communicator/impl/protocol/msn/EventManager.java @@ -10,6 +10,7 @@ import java.util.*; import net.java.sip.communicator.util.*; import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; import net.sf.jml.*; import net.sf.jml.impl.*; import net.sf.jml.net.*; @@ -25,15 +26,30 @@ import net.sf.jml.protocol.incoming.*; public class EventManager extends SessionAdapter { + /** + * The class logger. + */ private static final Logger logger = Logger.getLogger(EventManager.class); + /** + * Whether we are connected. + */ private boolean connected = false; + /** + * The timer for monitoring connection. + */ private Timer connectionTimer; + /** + * Event listeners. + */ private final List<MsnContactListEventListener> listeners = new Vector<MsnContactListEventListener>(); + /** + * The messenger. + */ private final BasicMessenger msnMessenger; /** @@ -143,6 +159,11 @@ public class EventManager } } + /** + * Called when there was timeout on the connection. + * @param socketSession + * @throws Exception + */ public void sessionTimeout(Session socketSession) throws Exception { Timer connectionTimer; @@ -164,8 +185,11 @@ public class EventManager { if(!connected && msnProvider.isRegistered()) { - msnProvider.unregister(false); - msnProvider.reconnect(SecurityAuthority.CONNECTION_FAILED); + msnProvider.fireRegistrationStateChanged( + msnProvider.getRegistrationState(), + RegistrationState.CONNECTION_FAILED, + RegistrationStateChangeEvent.REASON_NOT_SPECIFIED, + null); } } }, 20000); diff --git a/src/net/java/sip/communicator/impl/protocol/msn/OperationSetTypingNotificationsMsnImpl.java b/src/net/java/sip/communicator/impl/protocol/msn/OperationSetTypingNotificationsMsnImpl.java index bdcc12d..2a5c912 100644 --- a/src/net/java/sip/communicator/impl/protocol/msn/OperationSetTypingNotificationsMsnImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/msn/OperationSetTypingNotificationsMsnImpl.java @@ -22,6 +22,9 @@ import net.sf.jml.message.*; public class OperationSetTypingNotificationsMsnImpl extends AbstractOperationSetTypingNotifications<ProtocolProviderServiceMsnImpl> { + /** + * This class logger. + */ private static final Logger logger = Logger.getLogger(OperationSetTypingNotificationsMsnImpl.class); @@ -31,6 +34,9 @@ public class OperationSetTypingNotificationsMsnImpl */ private OperationSetPersistentPresenceMsnImpl opSetPersPresence = null; + /** + * The messenger. + */ private MsnMessenger messenger = null; /** @@ -99,12 +105,23 @@ public class OperationSetTypingNotificationsMsnImpl void setMessenger(MsnMessenger messenger) { this.messenger = messenger; - messenger.addMessageListener(new TypingListener()); + + if(messenger != null) + messenger.addMessageListener(new TypingListener()); } + /** + * Listens for typing notifications coming from the protocol. + */ private class TypingListener extends MsnAdapter { + /** + * Control message may indicate typing notification. + * @param switchboard + * @param message + * @param contact + */ public void controlMessageReceived(MsnSwitchboard switchboard, MsnControlMessage message, MsnContact contact) diff --git a/src/net/java/sip/communicator/impl/protocol/msn/ProtocolProviderServiceMsnImpl.java b/src/net/java/sip/communicator/impl/protocol/msn/ProtocolProviderServiceMsnImpl.java index 42b2fdd..589484f 100644 --- a/src/net/java/sip/communicator/impl/protocol/msn/ProtocolProviderServiceMsnImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/msn/ProtocolProviderServiceMsnImpl.java @@ -26,9 +26,15 @@ import net.sf.jml.impl.*; public class ProtocolProviderServiceMsnImpl extends AbstractProtocolProviderService { + /** + * Logger of this class + */ private static final Logger logger = Logger.getLogger(ProtocolProviderServiceMsnImpl.class); + /** + * The lib messenger. + */ private MsnMessenger messenger = null; /** @@ -46,8 +52,14 @@ public class ProtocolProviderServiceMsnImpl */ private SecurityAuthority authority = null; + /** + * Operation set for persistent presence. + */ private OperationSetPersistentPresenceMsnImpl persistentPresence = null; + /** + * Operation set for typing notifications. + */ private OperationSetTypingNotificationsMsnImpl typingNotifications = null; /** @@ -125,6 +137,7 @@ public class ProtocolProviderServiceMsnImpl /** * Connects and logins to the server * @param authority SecurityAuthority + * @param reasonCode * @throws OperationFailedException if login parameters * as server port are not correct */ @@ -234,6 +247,9 @@ public class ProtocolProviderServiceMsnImpl { if((messenger != null) && !logoutReceived) messenger.logout(); + + persistentPresence.setMessenger(null); + typingNotifications.setMessenger(null); } if(fireEvent) @@ -378,6 +394,10 @@ public class ProtocolProviderServiceMsnImpl private class MsnConnectionListener implements MsnMessengerListener { + /** + * Fired when login has completed. + * @param msnMessenger + */ public void loginCompleted(MsnMessenger msnMessenger) { logger.trace("loginCompleted " + msnMessenger.getActualMsnProtocol()); @@ -388,6 +408,10 @@ public class ProtocolProviderServiceMsnImpl null); } + /** + * Fire when lib logs out. + * @param msnMessenger + */ public void logout(MsnMessenger msnMessenger) { logger.trace("logout"); @@ -400,6 +424,11 @@ public class ProtocolProviderServiceMsnImpl } } + /** + * Fired when an exception has occurred. + * @param msnMessenger + * @param throwable + */ public void exceptionCaught(MsnMessenger msnMessenger, Throwable throwable) { @@ -430,8 +459,6 @@ public class ProtocolProviderServiceMsnImpl } else if(throwable instanceof UnknownHostException) { - unregister(false); - fireRegistrationStateChanged( getRegistrationState(), RegistrationState.CONNECTION_FAILED, @@ -484,6 +511,22 @@ public class ProtocolProviderServiceMsnImpl { logger.error("Error in Msn lib ", throwable); + if(throwable instanceof LoginException) + { + MsnActivator.getProtocolProviderFactory(). + storePassword(getAccountID(), null); + + fireRegistrationStateChanged( + getRegistrationState(), + RegistrationState.AUTHENTICATION_FAILED, + RegistrationStateChangeEvent + .REASON_AUTHENTICATION_FAILED, + null); + // We try to reconnect and ask user to retype + // password. + reconnect(SecurityAuthority.WRONG_PASSWORD); + } + // We don't want to disconnect on any error, that's why we're // commenting the following lines for now. // diff --git a/src/net/java/sip/communicator/impl/protocol/msn/ServerStoredContactListMsnImpl.java b/src/net/java/sip/communicator/impl/protocol/msn/ServerStoredContactListMsnImpl.java index 3bda285..e2d1a19 100644 --- a/src/net/java/sip/communicator/impl/protocol/msn/ServerStoredContactListMsnImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/msn/ServerStoredContactListMsnImpl.java @@ -71,6 +71,11 @@ public class ServerStoredContactListMsnImpl private Vector<String> skipAddEvent = new Vector<String>(); /** + * Contact list listener. + */ + ContactListListener contactListListener = null; + + /** * Creates a ServerStoredContactList wrapper for the specified BuddyList. * * @param parentOperationSet the operation set that created us and that @@ -917,7 +922,7 @@ public class ServerStoredContactListMsnImpl parentOperationSet.earlyStatusesDispatch(); // retreive offline messages - msnProvider.getMessenger().retreiveOfflineMessages(); + messenger.retreiveOfflineMessages(); } public void contactStatusChanged(MsnMessenger messenger, @@ -1268,6 +1273,18 @@ public class ServerStoredContactListMsnImpl */ void setMessenger(MsnMessenger messenger) { + if(messenger == null) + { + if(contactListModManager != null) + contactListModManager.removeModificationListener( + contactListModListenerImpl); + this.contactListModManager = null; + if(contactListListener != null) + this.messenger.removeContactListListener(contactListListener); + this.contactListListener = null; + this.messenger = null; + return; + } this.messenger = messenger; contactListModManager = @@ -1276,7 +1293,8 @@ public class ServerStoredContactListMsnImpl contactListModManager. addModificationListener(contactListModListenerImpl); - messenger.addContactListListener(new ContactListListener()); + contactListListener = new ContactListListener(); + messenger.addContactListListener(contactListListener); } /** diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/ProtocolProviderServiceYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/ProtocolProviderServiceYahooImpl.java index bd2ca79..6446479 100644 --- a/src/net/java/sip/communicator/impl/protocol/yahoo/ProtocolProviderServiceYahooImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/yahoo/ProtocolProviderServiceYahooImpl.java @@ -23,9 +23,15 @@ import ymsg.network.event.*; public class ProtocolProviderServiceYahooImpl extends AbstractProtocolProviderService { + /** + * This class logger. + */ private static final Logger logger = Logger.getLogger(ProtocolProviderServiceYahooImpl.class); + /** + * The current yahoo session. + */ private YahooSession yahooSession = null; /** @@ -48,8 +54,14 @@ public class ProtocolProviderServiceYahooImpl */ private SecurityAuthority authority = null; + /** + * The persistent presence operations set. + */ private OperationSetPersistentPresenceYahooImpl persistentPresence = null; + /** + * Typing notifications operations set. + */ private OperationSetTypingNotificationsYahooImpl typingNotifications = null; /** @@ -58,6 +70,10 @@ public class ProtocolProviderServiceYahooImpl private ProtocolIconYahooImpl yahooIcon = new ProtocolIconYahooImpl(); + /** + * The connection listener. + */ + private YahooConnectionListener connectionListener = null; /** * Returns the state of the registration of this protocol provider @@ -103,7 +119,6 @@ public class ProtocolProviderServiceYahooImpl * @param authority SecurityAuthority * @param authReasonCode the authentication reason code, which should * indicate why are making an authentication request - * @throws XMPPException if we cannot connect to the server - network problem * @throws OperationFailedException if login parameters * as server port are not correct */ @@ -152,7 +167,8 @@ public class ProtocolProviderServiceYahooImpl } yahooSession = new YahooSession(); - yahooSession.addSessionListener(new YahooConnectionListener()); + connectionListener = new YahooConnectionListener(); + yahooSession.addSessionListener(connectionListener); try { @@ -266,6 +282,12 @@ public class ProtocolProviderServiceYahooImpl try { + if(connectionListener != null && yahooSession != null) + { + yahooSession.removeSessionListener(connectionListener); + connectionListener = null; + } + if((yahooSession != null) && (yahooSession.getSessionStatus() == StatusConstants.MESSAGING)) yahooSession.logout(); @@ -422,8 +444,7 @@ public class ProtocolProviderServiceYahooImpl int reasonCode, String reason) { - if(newState.equals(RegistrationState.UNREGISTERED) || - newState.equals(RegistrationState.CONNECTION_FAILED)) + if(newState.equals(RegistrationState.UNREGISTERED)) { unregister(false); yahooSession = null; @@ -441,10 +462,11 @@ public class ProtocolProviderServiceYahooImpl { /** * Yahoo has logged us off the system, or the connection was lost - **/ + * + * @param ev the event + */ public void connectionClosed(SessionEvent ev) { - unregister(true); if(isRegistered()) fireRegistrationStateChanged( getRegistrationState(), @@ -452,6 +474,10 @@ public class ProtocolProviderServiceYahooImpl RegistrationStateChangeEvent.REASON_NOT_SPECIFIED, null); } + /** + * Some exception has occurred in stack. + * @param ev + */ public void inputExceptionThrown(SessionExceptionEvent ev) { if(ev.getException() instanceof YMSG9BadFormatException) diff --git a/src/net/java/sip/communicator/plugin/reconnectplugin/ReconnectPluginActivator.java b/src/net/java/sip/communicator/plugin/reconnectplugin/ReconnectPluginActivator.java new file mode 100644 index 0000000..0fc03dc --- /dev/null +++ b/src/net/java/sip/communicator/plugin/reconnectplugin/ReconnectPluginActivator.java @@ -0,0 +1,581 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.plugin.reconnectplugin; + +import java.net.*; +import java.util.*; +import java.util.ArrayList; + +import net.java.sip.communicator.service.gui.*; +import net.java.sip.communicator.service.netaddr.*; +import net.java.sip.communicator.service.netaddr.event.*; +import net.java.sip.communicator.service.notification.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.*; + +import org.osgi.framework.*; + +/** + * Activates the reconnect plug-in. + * + * @author Damian Minkov + */ +public class ReconnectPluginActivator + implements BundleActivator, + ServiceListener, + NetworkConfigurationChangeListener, + RegistrationStateChangeListener +{ + /** + * Logger of this class + */ + private static final Logger logger + = Logger.getLogger(ReconnectPluginActivator.class); + + /** + * The current BundleContext. + */ + private static BundleContext bundleContext = null; + + /** + * The ui service. + */ + private static UIService uiService; + + /** + * Notification service. + */ + private static NotificationService notificationService; + + /** + * Network address manager service will inform us for changes in + * network configuration. + */ + private NetworkAddressManagerService networkAddressManagerService = null; + + /** + * Holds every protocol provider which is can be reconnected and + * a list of the available and up interfaces when the provider was + * registered. When a provider is unregistered it is removed + * from this collection. + */ + private Map<ProtocolProviderService, List<String>> autoReconnEnabledProviders = + new Hashtable<ProtocolProviderService, List<String>>(); + + /** + * Holds the currently reconnecting providers and their reconnect tasks. + * When they get connected they are removed from this collection. + */ + private Map<ProtocolProviderService, ReconnectTask> currentlyReconnecting = + new Hashtable<ProtocolProviderService, ReconnectTask>(); + + /** + * If network is down we save here the providers which need to be reconnected. + */ + private Set<ProtocolProviderService> needsReconnection = + new HashSet<ProtocolProviderService>(); + + /** + * A list of providers on which we have called unregister. This is a + * way to differ our unregister calls from calls coming from user, wanting + * to stop all reconnects. + */ + private List<ProtocolProviderService> unregisteredProviders + = new ArrayList<ProtocolProviderService>(); + + /** + * A list of currently connected interfaces. If empty network is down. + */ + private Set<String> connectedInterfaces = new HashSet<String>(); + + /** + * Timer for scheduling all reconnect operations. + */ + private Timer timer = null; + + /** + * Start of the delay interval when starting a reconnect. + */ + private static final int RECONNECT_DELAY_MIN = 8; // sec + + /** + * The end of the interval for the initial reconnect. + */ + private static final int RECONNECT_DELAY_MAX = 30; // sec + + /** + * Max value for growing the reconnect delay, all subsequent reconnects + * use this maximum delay. + */ + private static final int MAX_RECONNECT_DELAY = 300; // sec + + /** + * Network notifications event type. + */ + public static final String NETWORK_NOTIFICATIONS = "NetowrkNotifications"; + + /** + * Starts this bundle + * + * @param bundleContext BundleContext + * @throws Exception + */ + public void start(BundleContext bundleContext) throws Exception + { + try + { + logger.logEntry(); + ReconnectPluginActivator.bundleContext = bundleContext; + } + finally + { + logger.logExit(); + } + + bundleContext.addServiceListener(this); + + if(timer == null) + timer = new Timer("Reconnect timer"); + + ServiceReference serviceReference = bundleContext.getServiceReference( + NetworkAddressManagerService.class.getName()); + + this.networkAddressManagerService = + (NetworkAddressManagerService)bundleContext + .getService(serviceReference); + + this.networkAddressManagerService + .addNetworkConfigurationChangeListener(this); + + ServiceReference[] protocolProviderRefs = null; + try + { + protocolProviderRefs = bundleContext.getServiceReferences( + ProtocolProviderService.class.getName(), null); + } + catch (InvalidSyntaxException ex) + { + // this shouldn't happen since we're providing no parameter string + // but let's log just in case. + logger.error( + "Error while retrieving service refs", ex); + return; + } + + // in case we found any + if (protocolProviderRefs != null) + { + logger.debug("Found " + + protocolProviderRefs.length + + " already installed providers."); + for (int i = 0; i < protocolProviderRefs.length; i++) + { + ProtocolProviderService provider + = (ProtocolProviderService) bundleContext + .getService(protocolProviderRefs[i]); + + this.handleProviderAdded(provider); + } + } + } + + /** + * Stop the bundle. Nothing to stop for now. + * @param bundleContext + * @throws Exception + */ + public void stop(BundleContext bundleContext) + throws Exception + { + if(timer != null) + { + timer.cancel(); + timer = null; + } + } + + /** + * Returns the <tt>UIService</tt> obtained from the bundle context. + * + * @return the <tt>UIService</tt> obtained from the bundle context + */ + public static UIService getUIService() + { + if (uiService == null) + { + ServiceReference uiReference = + bundleContext.getServiceReference(UIService.class.getName()); + + uiService = + (UIService) bundleContext + .getService(uiReference); + } + + return uiService; + } + + /** + * Returns the <tt>NotificationService</tt> obtained from the bundle context. + * + * @return the <tt>NotificationService</tt> obtained from the bundle context + */ + public static NotificationService getNotificationService() + { + if (notificationService == null) + { + ServiceReference serviceReference = bundleContext + .getServiceReference(NotificationService.class.getName()); + + notificationService = (NotificationService) bundleContext + .getService(serviceReference); + + notificationService.registerDefaultNotificationForEvent( + NETWORK_NOTIFICATIONS, + NotificationService.ACTION_POPUP_MESSAGE, + null, + null); + } + + return notificationService; + } + + /** + * When new protocol provider is registered we add needed listeners. + * + * @param serviceEvent ServiceEvent + */ + public void serviceChanged(ServiceEvent serviceEvent) + { + ServiceReference serviceRef = serviceEvent.getServiceReference(); + + // if the event is caused by a bundle being stopped, we don't want to + // know + if (serviceRef.getBundle().getState() == Bundle.STOPPING) + { + return; + } + + Object sService = bundleContext.getService(serviceRef); + + logger.trace("Received a service event for: " + + sService.getClass().getName()); + + if(sService instanceof NetworkAddressManagerService) + { + switch (serviceEvent.getType()) + { + case ServiceEvent.REGISTERED: + if(this.networkAddressManagerService != null) + break; + + this.networkAddressManagerService = + (NetworkAddressManagerService)sService; + networkAddressManagerService + .addNetworkConfigurationChangeListener(this); + break; + case ServiceEvent.UNREGISTERING: + ((NetworkAddressManagerService)sService) + .removeNetworkConfigurationChangeListener(this); + break; + } + + return; + } + + // we don't care if the source service is not a protocol provider + if (!(sService instanceof ProtocolProviderService)) + return; + + logger.debug("Service is a protocol provider."); + switch (serviceEvent.getType()) + { + case ServiceEvent.REGISTERED: + this.handleProviderAdded((ProtocolProviderService)sService); + break; + + case ServiceEvent.UNREGISTERING: + this.handleProviderRemoved( (ProtocolProviderService) sService); + break; + } + } + + /** + * Add listeners to newly registered protocols. + * + * @param provider ProtocolProviderService + */ + private void handleProviderAdded(ProtocolProviderService provider) + { + logger.debug("Adding protocol provider " + provider.getProtocolName()); + + if(provider instanceof ProtocolProviderService) + { + provider.addRegistrationStateChangeListener(this); + } + } + + /** + * Stop listening for events as the provider is removed. + * + * @param provider the ProtocolProviderService that has been unregistered. + */ + private void handleProviderRemoved(ProtocolProviderService provider) + { + if(provider instanceof ProtocolProviderService) + { + provider.removeRegistrationStateChangeListener(this); + } + } + + /** + * Fired when a change has occurred in the computer network configuration. + * + * @param event the change event. + */ + public synchronized void configurationChanged(ChangeEvent event) + { + if(!(event.getSource() instanceof NetworkInterface)) + return; + + NetworkInterface iface = (NetworkInterface)event.getSource(); + + if(event.getType() == ChangeEvent.IFACE_UP) + { + // no connection so one is up, lets connect + if(connectedInterfaces.size() == 0) + { + Iterator<ProtocolProviderService> iter = + needsReconnection.iterator(); + while (iter.hasNext()) + { + ProtocolProviderService pp = iter.next(); + if(currentlyReconnecting.containsKey(pp)) + { + // now lets cancel it and schedule it again + // so it will use this iface + currentlyReconnecting.get(pp).cancel(); + currentlyReconnecting.remove(pp); + } + + reconnect(pp); + } + + needsReconnection.clear(); + } + + connectedInterfaces.add(iface.getName()); + } + else if(event.getType() == ChangeEvent.IFACE_DOWN) + { + connectedInterfaces.remove(iface.getName()); + + // one is down and at least one more is connected + if(connectedInterfaces.size() > 0) + { + // lets reconnect all that was connected when this one was + // available, cause they maybe using it + Iterator<Map.Entry<ProtocolProviderService, List<String>>> iter = + autoReconnEnabledProviders.entrySet().iterator(); + while (iter.hasNext()) + { + Map.Entry<ProtocolProviderService, List<String>> entry + = iter.next(); + + if(entry.getValue().contains(iface.getName())) + { + ProtocolProviderService pp = entry.getKey(); + // hum someone is reconnecting, lets cancel and + // schedule it again + if(currentlyReconnecting.containsKey(pp)) + { + currentlyReconnecting.get(pp).cancel(); + currentlyReconnecting.remove(pp); + } + + reconnect(pp); + } + } + } + else + { + // we must disconnect every pp and put all to be need of reconnecting + needsReconnection.addAll(autoReconnEnabledProviders.keySet()); + + Iterator<ProtocolProviderService> iter = + needsReconnection.iterator(); + while (iter.hasNext()) + { + ProtocolProviderService pp = iter.next(); + try + { + unregisteredProviders.add(pp); + pp.unregister(); + } catch (Exception e) + { + logger.error("Cannot unregister provider", e); + } + } + + connectedInterfaces.clear(); + + logger.trace("Network is down!"); + getNotificationService().fireNotification( + NETWORK_NOTIFICATIONS, + "Network is down!", + "", + null, + null); + } + } + } + + /** + * The method is called by a <code>ProtocolProviderService</code> + * implementation whenever a change in the registration state of the + * corresponding provider had occurred. + * + * @param evt the event describing the status change. + */ + public synchronized void registrationStateChanged(RegistrationStateChangeEvent evt) + { + // we don't care about protocol providers that don't support + // reconnection + if(!(evt.getSource() instanceof ProtocolProviderService)) + return; + + ProtocolProviderService pp = (ProtocolProviderService)evt.getSource(); + + if(evt.getNewState().equals(RegistrationState.CONNECTION_FAILED)) + { + // if this pp is already in needsReconnection, it means + // we got conn failed cause the pp has tried to unregister + // with sending network packet + // but this unregister is scheduled from us so skip + if(needsReconnection.contains(pp)) + return; + + if(connectedInterfaces.size() == 0) + needsReconnection.add(pp); + else + { + // network is up but something happen and cannot reconnect + // strange lets try again after some time + reconnect(pp); + } + + // unregister can finish and with connection failed, + // the protocol is unable to unregister + unregisteredProviders.remove(pp); + } + else if(evt.getNewState().equals(RegistrationState.REGISTERED)) + { + autoReconnEnabledProviders.put( + (ProtocolProviderService)evt.getSource(), + new ArrayList<String>(connectedInterfaces)); + + currentlyReconnecting.remove(pp); + } + else if(evt.getNewState().equals(RegistrationState.UNREGISTERED)) + { + autoReconnEnabledProviders.remove( + (ProtocolProviderService)evt.getSource()); + + if(!unregisteredProviders.contains(pp) + && currentlyReconnecting.containsKey(pp)) + { + currentlyReconnecting.remove(pp).cancel(); + } + unregisteredProviders.remove(pp); + } + } + + /** + * Method to schedule a reconnect for a protocol provider. + * @param pp the provider. + */ + private void reconnect(ProtocolProviderService pp) + { + long delay; + + if(currentlyReconnecting.containsKey(pp)) + { + delay = currentlyReconnecting.get(pp).delay; + + // we never stop trying + //if(delay == MAX_RECONNECT_DELAY*1000) + // return; + + delay = Math.min(delay * 2, MAX_RECONNECT_DELAY*1000); + } + else + { + delay = (long)(RECONNECT_DELAY_MIN + + Math.random() * RECONNECT_DELAY_MAX)*1000; + } + + // as we will reconnect, lets unregister + try + { + unregisteredProviders.add(pp); + pp.unregister(); + } catch (OperationFailedException e) + { + logger.error("Cannot unregister provider", e); + } + + ReconnectTask task = new ReconnectTask(pp); + task.delay = delay; + currentlyReconnecting.put(pp, task); + + logger.trace("Reconnect " + pp + " after " + delay + " ms."); + timer.schedule(task, delay); + } + + /** + * The task executed by the timer when time for reconnect comes. + */ + private class ReconnectTask + extends TimerTask + { + /** + * The provider to reconnect. + */ + private ProtocolProviderService provider; + + /** + * The delay with which was this task scheduled. + */ + private long delay; + + /** + * Creates the task. + * @param provider + */ + public ReconnectTask(ProtocolProviderService provider) + { + this.provider = provider; + } + + /** + * Reconnects the provider. + */ + public void run() + { + try + { + logger.trace("Start reconnecting!"); + + provider.register( + getUIService().getDefaultSecurityAuthority(provider)); + } catch (OperationFailedException ex) + { + logger.error("cannot reregister provider will keep going", ex); + } + } + } +} diff --git a/src/net/java/sip/communicator/plugin/reconnectplugin/reconnectplugin.manifest.mf b/src/net/java/sip/communicator/plugin/reconnectplugin/reconnectplugin.manifest.mf new file mode 100644 index 0000000..24d859e --- /dev/null +++ b/src/net/java/sip/communicator/plugin/reconnectplugin/reconnectplugin.manifest.mf @@ -0,0 +1,17 @@ +Bundle-Activator: net.java.sip.communicator.plugin.reconnectplugin.ReconnectPluginActivator +Bundle-Name: ReconnectPlugin +Bundle-Description: A bundle that implements the Reconnect Plugin Package. +Bundle-Vendor: sip-communicator.org +Bundle-Version: 0.0.1 +System-Bundle: yes +Import-Package: org.osgi.framework, + net.java.sip.communicator.service.netaddr, + net.java.sip.communicator.service.netaddr.event, + net.java.sip.communicator.service.configuration, + net.java.sip.communicator.service.gui, + net.java.sip.communicator.service.notification, + net.java.sip.communicator.service.protocol, + net.java.sip.communicator.service.protocol.event, + net.java.sip.communicator.service.resources, + net.java.sip.communicator.util, + net.java.sip.communicator.util.swing
\ No newline at end of file diff --git a/src/net/java/sip/communicator/service/netaddr/NetworkAddressManagerService.java b/src/net/java/sip/communicator/service/netaddr/NetworkAddressManagerService.java index eb1edc5..478dd30 100644 --- a/src/net/java/sip/communicator/service/netaddr/NetworkAddressManagerService.java +++ b/src/net/java/sip/communicator/service/netaddr/NetworkAddressManagerService.java @@ -9,6 +9,8 @@ package net.java.sip.communicator.service.netaddr; import java.net.*; import java.io.*; +import net.java.sip.communicator.service.netaddr.event.*; + /** * The NetworkAddressManagerService takes care of problems such as * @author Emil Ivov @@ -119,4 +121,19 @@ public interface NetworkAddressManagerService IOException, BindException; + /** + * Adds new <tt>NetworkConfigurationChangeListener</tt> which will + * be informed for network configuration changes. + * @param listener the listener. + */ + public void addNetworkConfigurationChangeListener( + NetworkConfigurationChangeListener listener); + + /** + * Remove <tt>NetworkConfigurationChangeListener</tt>. + * @param listener the listener. + */ + public void removeNetworkConfigurationChangeListener( + NetworkConfigurationChangeListener listener); + } diff --git a/src/net/java/sip/communicator/service/netaddr/event/ChangeEvent.java b/src/net/java/sip/communicator/service/netaddr/event/ChangeEvent.java new file mode 100644 index 0000000..a31d9fc --- /dev/null +++ b/src/net/java/sip/communicator/service/netaddr/event/ChangeEvent.java @@ -0,0 +1,100 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.service.netaddr.event; + +/** + * A ChangeEvent is fired on change of the network configuration of the computer. + * + * @author Damian Minkov + */ +public class ChangeEvent + extends java.util.EventObject +{ + /** + * Event type for interface going up. + */ + public static final int IFACE_DOWN = 0; + + /** + * Event type for interface going down. + */ + public static final int IFACE_UP = 1; + + /** + * The type of the current event. + */ + private int type = -1; + + /** + * Whether this event is after computer have been suspended. + */ + private boolean standby = false; + + /** + * Creates event. + * @param source the source of the event. + * @param type the type of the event. + */ + public ChangeEvent(Object source, int type) + { + super(source); + + this.type = type; + } + + /** + * Creates event. + * @param source the source of the event. + * @param type the type of the event. + * @param standby is the event after a suspend of the computer. + */ + public ChangeEvent(Object source, int type, boolean standby) + { + this(source, type); + + this.standby = standby; + } + + /** + * The type of this event. + * @return the type + */ + public int getType() + { + return type; + } + + /** + * Whether this event is after suspend of the computer. + * @return the standby + */ + public boolean isStandby() + { + return standby; + } + + /** + * Overrides toString method. + * @return string representing the event. + */ + @Override + public String toString() + { + StringBuilder buff = new StringBuilder(); + buff.append("ChangeEvent "); + + switch(type) + { + case IFACE_DOWN: buff.append("Interface down"); break; + case IFACE_UP: buff.append("Interface up"); break; + } + + buff.append(", standby=" + standby); + + return buff.toString(); + } +} diff --git a/src/net/java/sip/communicator/service/netaddr/event/NetworkConfigurationChangeListener.java b/src/net/java/sip/communicator/service/netaddr/event/NetworkConfigurationChangeListener.java new file mode 100644 index 0000000..7318640 --- /dev/null +++ b/src/net/java/sip/communicator/service/netaddr/event/NetworkConfigurationChangeListener.java @@ -0,0 +1,22 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.service.netaddr.event; + +/** + * Listens for network changes in the computer configuration. + * + * @author Damian Minkov + */ +public interface NetworkConfigurationChangeListener +{ + /** + * Fired when a change has occurred in the computer network configuration. + * + * @param event the change event. + */ + public void configurationChanged(ChangeEvent event); +} |