diff options
18 files changed, 1261 insertions, 45 deletions
@@ -909,7 +909,7 @@ bundle-filehistory,bundle-metahistory,bundle-metahistory-slick, bundle-plugin-facebookaccregwizz, bundle-bouncycastle,bundle-plugin-otr,bundle-plugin-iptelaccregwizz, - bundle-contactsource"/> + bundle-contactsource,bundle-plugin-reconnect"/> <!--BUNDLE-SC-LAUNCHER--> <target name="bundle-sc-launcher"> @@ -2325,4 +2325,14 @@ org.apache.http.util"/> prefix="net/java/sip/communicator/service/contactsource"/> </jar> </target> + + <!-- BUNDLE-PLUGIN-RECONNECT --> + <target name="bundle-plugin-reconnect"> + <!-- Creates a bundle for the reconnect plugin.--> + <jar compress="false" destfile="${bundles.dest}/reconnectplugin.jar" + manifest="${src}/net/java/sip/communicator/plugin/reconnectplugin/reconnectplugin.manifest.mf"> + <zipfileset dir="${dest}/net/java/sip/communicator/plugin/reconnectplugin" + prefix="net/java/sip/communicator/plugin/reconnectplugin"/> + </jar> + </target> </project> diff --git a/lib/felix.client.run.properties b/lib/felix.client.run.properties index d7232ad..442d318 100644 --- a/lib/felix.client.run.properties +++ b/lib/felix.client.run.properties @@ -76,6 +76,7 @@ felix.auto.start.52= \ reference:file:sc-bundles/protocol-gibberish.jar \ reference:file:sc-bundles/protocol-ssh.jar \ reference:file:sc-bundles/netaddr.jar \ + reference:file:sc-bundles/reconnectplugin.jar \ reference:file:sc-bundles/protocol-zeroconf.jar \ reference:file:sc-bundles/protocol-irc.jar \ reference:file:sc-bundles/protocol-dict.jar diff --git a/resources/install/build.xml b/resources/install/build.xml index 0378232..f587091 100644 --- a/resources/install/build.xml +++ b/resources/install/build.xml @@ -401,7 +401,7 @@ <entry key="last_version" value="${sip-communicator.version}" /> <entry key="download_link" value="http://download.sip-communicator.org/nightly/windows/${package.name}-${sip-communicator.version}.exe" /> - <entry key="changes_html" value="changes.html" /> + <entry key="changes_html" value="updates/index.html" /> </propertyfile> <!-- 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); +} |