com.limegroup.gnutella.RouterService.java Source code

Java tutorial

Introduction

Here is the source code for com.limegroup.gnutella.RouterService.java

Source

package com.limegroup.gnutella;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.limegroup.bittorrent.TorrentManager;
import com.limegroup.gnutella.altlocs.AltLocManager;
import com.limegroup.gnutella.auth.ContentManager;
import com.limegroup.gnutella.bootstrap.BootstrapServerManager;
import com.limegroup.gnutella.browser.HTTPAcceptor;
import com.limegroup.gnutella.browser.MagnetOptions;
import com.limegroup.gnutella.chat.ChatManager;
import com.limegroup.gnutella.chat.Chatter;
import com.limegroup.gnutella.downloader.CantResumeException;
import com.limegroup.gnutella.downloader.HTTPDownloader;
import com.limegroup.gnutella.downloader.IncompleteFileManager;
import com.limegroup.gnutella.filters.IPFilter;
import com.limegroup.gnutella.filters.MutableGUIDFilter;
import com.limegroup.gnutella.filters.SpamFilter;
import com.limegroup.gnutella.handshaking.HeaderNames;
import com.limegroup.gnutella.licenses.LicenseFactory;
import com.limegroup.gnutella.messages.QueryRequest;
import com.limegroup.gnutella.messages.SecureMessageVerifier;
import com.limegroup.gnutella.messages.StaticMessages;
import com.limegroup.gnutella.messages.vendor.HeaderUpdateVendorMessage;
import com.limegroup.gnutella.search.QueryDispatcher;
import com.limegroup.gnutella.search.SearchResultHandler;
import com.limegroup.gnutella.settings.ApplicationSettings;
import com.limegroup.gnutella.settings.ConnectionSettings;
import com.limegroup.gnutella.settings.FilterSettings;
import com.limegroup.gnutella.settings.SearchSettings;
import com.limegroup.gnutella.settings.SettingsHandler;
import com.limegroup.gnutella.settings.SharingSettings;
import com.limegroup.gnutella.settings.SimppSettingsManager;
import com.limegroup.gnutella.simpp.SimppManager;
import com.limegroup.gnutella.spam.RatingTable;
import com.limegroup.gnutella.statistics.OutOfBandThroughputStat;
import com.limegroup.gnutella.tigertree.TigerTreeCache;
import com.limegroup.gnutella.udpconnect.UDPMultiplexor;
import com.limegroup.gnutella.updates.UpdateManager;
import com.limegroup.gnutella.upelection.PromotionManager;
import com.limegroup.gnutella.uploader.NormalUploadState;
import com.limegroup.gnutella.util.IpPortSet;
import com.limegroup.gnutella.util.ManagedThread;
import com.limegroup.gnutella.util.NetworkUtils;
import com.limegroup.gnutella.util.SimpleTimer;
import com.limegroup.gnutella.util.ThreadFactory;
import com.limegroup.gnutella.version.UpdateHandler;
import com.limegroup.gnutella.xml.MetaFileManager;

/**
 * A facade for the entire LimeWire backend.  This is the GUI's primary way of
 * communicating with the backend.  RouterService constructs the backend 
 * components.  Typical use is as follows:
 *
 * <pre>
 * RouterService rs = new RouterService(ActivityCallback);
 * rs.start();
 * rs.query(...);
 * rs.download(...);
 * rs.shutdown();
 * </pre>
 *
 * The methods of this class are numerous, but they tend to fall into one of the
 * following categories:
 *
 * <ul> 
 * <li><b>Connecting and disconnecting</b>: connect, disconnect,
 *     connectToHostBlocking, connectToHostAsynchronously, 
 *     connectToGroup, removeConnection, getNumConnections
 * <li><b>Searching and downloading</b>: query, browse, score, matchesType,
 *     isMandragoreWorm, download
 * <li><b>Notification of SettingsManager changes</b>:
 *     setKeepAlive, setListeningPort, adjustSpamFilters, refreshBannedIPs
 * <li><b>HostCatcher and horizon</b>: clearHostCatcher, getHosts, removeHost,
 *     getNumHosts, getNumFiles, getTotalFileSize, setAlwaysNotifyKnownHost,
 *     updateHorizon.  <i>(HostCatcher has changed dramatically on
 *     pong-caching-branch and query-routing3-branch of CVS, so these methods
 *     will probably be obsolete in the future.)</i>
 * <li><b>Statistics</b>: getNumLocalSearches, getNumSharedFiles, 
 *      getTotalMessages, getTotalDroppedMessages, getTotalRouteErrors,
 *      getNumPendingShared
 * </ul> 
 */
public class RouterService {

    private static final Log LOG = LogFactory.getLog(RouterService.class);

    /**
     * <tt>FileManager</tt> instance that manages access to shared files.
     */
    private static FileManager fileManager = new MetaFileManager();

    /**
     * Timer similar to java.util.Timer, which was not available on 1.1.8.
     */
    private static final SimpleTimer timer = new SimpleTimer(true);

    /**
     * <tt>Acceptor</tt> instance for accepting new connections, HTTP
     * requests, etc.
     */
    private static final Acceptor acceptor = new Acceptor();

    /**
     * <tt>TorrentManager</tt> instance for handling torrents
     */
    private static final TorrentManager torrentManager = new TorrentManager();

    /**
     * ConnectionDispatcher instance that will dispatch incoming connections to
     * the appropriate managers.
     */
    private static final ConnectionDispatcher dispatcher = new ConnectionDispatcher();

    /**
     * <tt>HTTPAcceptor</tt> instance for accepting magnet requests, etc.
     */
    private static HTTPAcceptor httpAcceptor;

    /**
     * Initialize the class that manages all TCP connections.
     */
    private static ConnectionManager manager = new ConnectionManager();

    /**
     * <tt>HostCatcher</tt> that handles Gnutella pongs.  Only not final
      * for tests.
     */
    private static HostCatcher catcher = new HostCatcher();

    /**
     * <tt>DownloadManager</tt> for handling HTTP downloading.
     */
    private static DownloadManager downloader = new DownloadManager();

    /**
     * <tt>UploadManager</tt> for handling HTTP uploading.
     */
    private static UploadManager uploadManager = new UploadManager();

    /**
     * <tt>PushManager</tt> for handling push requests.
     */
    private static PushManager pushManager = new PushManager();

    /**
     * <tt>PromotionManager</tt> for handling promotions to Ultrapeer.
     */
    private static PromotionManager promotionManager = new PromotionManager();

    private static ResponseVerifier VERIFIER = new ResponseVerifier();

    /**
     * <tt>Statistics</tt> class for managing statistics.
     */
    private static final Statistics STATISTICS = Statistics.instance();

    /**
     * Constant for the <tt>UDPService</tt> instance that handles UDP 
     * messages.
     */
    private static final UDPService UDPSERVICE = UDPService.instance();

    /**
     * Constant for the <tt>SearchResultHandler</tt> class that processes
     * search results sent back to this client.
     */
    private static final SearchResultHandler RESULT_HANDLER = new SearchResultHandler();

    /**
     * The manager of altlocs
     */
    private static AltLocManager altManager = AltLocManager.instance();

    /** Variable for the <tt>SecureMessageVerifier</tt> that verifies secure messages. */
    private static SecureMessageVerifier secureMessageVerifier = new SecureMessageVerifier();

    /**
     * The content manager
     */
    private static ContentManager contentManager = new ContentManager();

    /**
     * isShuttingDown flag
     */
    private static boolean isShuttingDown;

    /**
     * Variable for the <tt>ActivityCallback</tt> instance.
     */
    private static ActivityCallback callback;

    /**
     * Variable for the <tt>MessageRouter</tt> that routes Gnutella
     * messages.
     */
    private static MessageRouter router;

    /**
     * A list of items that require running prior to shutting down LW.
     */
    private static final List SHUTDOWN_ITEMS = Collections.synchronizedList(new LinkedList());

    /**
     * Variable for whether or not that backend threads have been started.
     * 0 - nothing started
     * 1 - pre/while gui tasks started
     * 2 - everything started
     * 3 - shutting down
     * 4 - shut down
     */
    private static volatile int _state;

    /**
     * Long for the last time this host originated a query.
     */
    private static long _lastQueryTime = 0L;

    /**
     * Whether or not we are running at full power.
     */
    private static boolean _fullPower = true;

    private static final byte[] MYGUID;
    static {
        byte[] myguid = null;
        try {
            myguid = GUID.fromHexString(ApplicationSettings.CLIENT_ID.getValue());
        } catch (IllegalArgumentException iae) {
            myguid = GUID.makeGuid();
            ApplicationSettings.CLIENT_ID.setValue((new GUID(myguid)).toHexString());
        }
        MYGUID = myguid;
    }

    /**
     * Creates a new <tt>RouterService</tt> instance.  This fully constructs 
     * the backend.
     *
     * @param callback the <tt>ActivityCallback</tt> instance to use for
     *  making callbacks
     */
    public RouterService(ActivityCallback callback) {
        this(callback, new StandardMessageRouter());
    }

    /**
     * Constructor for the Peer Server.
     */
    public RouterService(ActivityCallback ac, MessageRouter mr, FileManager fm) {
        this(ac, mr);
        RouterService.fileManager = fm;
    }

    /**
     * Creates a new <tt>RouterService</tt> instance with special message
      * handling code.  Typically this constructor is only used for testing.
     *
     * @param callback the <tt>ActivityCallback</tt> instance to use for
     *  making callbacks
      * @param router the <tt>MessageRouter</tt> instance to use for handling
      *  all messages
     */
    public RouterService(ActivityCallback callback, MessageRouter router) {
        RouterService.callback = callback;
        fileManager.registerFileManagerEventListener(callback);
        RouterService.router = router;
    }

    /**
     * Performs startup tasks that should happen while the GUI loads
     */
    public static void asyncGuiInit() {

        synchronized (RouterService.class) {
            if (_state > 0) // already did this?
                return;
            else
                _state = 1;
        }

        ThreadFactory.startThread(new Initializer(), "async gui initializer");
    }

    /**
     * performs the tasks usually run while the gui is initializing synchronously
     * to be used for tests and when running only the core
     */
    public static void preGuiInit() {

        synchronized (RouterService.class) {
            if (_state > 0) // already did this?
                return;
            else
                _state = 1;
        }

        (new Initializer()).run();
    }

    private static class Initializer implements Runnable {
        public void run() {
            //add more while-gui init tasks here
            RouterService.getAcceptor().init();
        }
    }

    /**
     * Starts various threads and tasks once all core classes have
     * been constructed.
     */
    public void start() {
        synchronized (RouterService.class) {
            LOG.trace("START RouterService");

            if (isStarted())
                return;

            preGuiInit();
            _state = 2;

            // Now, link all the pieces together, starting the various threads.

            //Note: SimppManager and SimppSettingsManager must go first to make
            //sure all the settings are created with the simpp values. Other
            //components may rely on the settings, so they must have the right
            //values when they are being initialized.
            LOG.trace("START SimppManager.instance");
            callback.componentLoading("SIMPP_MANAGER");
            SimppManager.instance();//initialize
            LOG.trace("STOP SimppManager.instance");

            LOG.trace("START SimppSettingsManager.instance");
            SimppSettingsManager.instance();
            LOG.trace("STOP SimppSettingsManager.instance");

            LOG.trace("START ContentManager");
            contentManager.initialize();
            LOG.trace("STOP ContentManager");

            LOG.trace("START MessageRouter");
            callback.componentLoading("MESSAGE_ROUTER");
            router.initialize();
            LOG.trace("STOPMessageRouter");

            LOG.trace("START Acceptor");
            callback.componentLoading("ACCEPTOR");
            acceptor.start();
            LOG.trace("STOP Acceptor");

            LOG.trace("START ConnectionManager");
            callback.componentLoading("CONNECTION_MANAGER");
            manager.initialize();
            LOG.trace("STOP ConnectionManager");

            LOG.trace("START DownloadManager");
            downloader.initialize();
            LOG.trace("STOP DownloadManager");

            LOG.trace("START SupernodeAssigner");
            SupernodeAssigner sa = new SupernodeAssigner(uploadManager, downloader, manager);
            sa.start();
            LOG.trace("STOP SupernodeAssigner");

            // THIS MUST BE BEFORE THE CONNECT (below)
            // OTHERWISE WE WILL ALWAYS CONNECT TO GWEBCACHES
            LOG.trace("START HostCatcher.initialize");
            callback.componentLoading("HOST_CATCHER");
            catcher.initialize();
            LOG.trace("STOP HostCatcher.initialize");

            if (ConnectionSettings.CONNECT_ON_STARTUP.getValue()) {
                // Make sure connections come up ultra-fast (beyond default keepAlive)      
                int outgoing = ConnectionSettings.NUM_CONNECTIONS.getValue();
                if (outgoing > 0) {
                    LOG.trace("START connect");
                    connect();
                    LOG.trace("STOP connect");
                }
            }
            // Asynchronously load files now that the GUI is up, notifying
            // callback.
            LOG.trace("START FileManager");
            callback.componentLoading("FILE_MANAGER");
            fileManager.start();
            LOG.trace("STOP FileManager");

            // Restore any downloads in progress.
            LOG.trace("START DownloadManager.postGuiInit");
            callback.componentLoading("DOWNLOAD_MANAGER_POST_GUI");
            downloader.postGuiInit();
            LOG.trace("STOP DownloadManager.postGuiInit");

            LOG.trace("START UpdateManager.instance");
            callback.componentLoading("UPDATE_MANAGER");
            UpdateManager.instance();
            UpdateHandler.instance();
            LOG.trace("STOP UpdateManager.instance");

            LOG.trace("START QueryUnicaster");
            callback.componentLoading("QUERY_UNICASTER");
            QueryUnicaster.instance().start();
            LOG.trace("STOP QueryUnicaster");

            LOG.trace("START HTTPAcceptor");
            callback.componentLoading("HTTPACCEPTOR");
            httpAcceptor = new HTTPAcceptor();
            httpAcceptor.start();
            LOG.trace("STOP HTTPAcceptor");

            LOG.trace("START Pinger");
            callback.componentLoading("PINGER");
            Pinger.instance().start();
            LOG.trace("STOP Pinger");

            LOG.trace("START ConnectionWatchdog");
            callback.componentLoading("CONNECTION_WATCHDOG");
            ConnectionWatchdog.instance().start();
            LOG.trace("STOP ConnectionWatchdog");

            LOG.trace("START SavedFileManager");
            callback.componentLoading("SAVED_FILE_MANAGER");
            SavedFileManager.instance();
            LOG.trace("STOP SavedFileManager");

            LOG.trace("START loading spam data");
            RatingTable.instance();
            LOG.trace("START loading spam data");

            LOG.trace("START loading StaticMessages");
            StaticMessages.initialize();
            LOG.trace("END loading StaticMessages");

            LOG.trace("START TorrentManager");
            torrentManager.initialize();
            LOG.trace("STOP TorrentManager");

            if (ApplicationSettings.AUTOMATIC_MANUAL_GC.getValue())
                startManualGCThread();

            LOG.trace("STOP RouterService.");
        }
    }

    /**
     * Starts a manual GC thread.
     */
    private void startManualGCThread() {
        Thread t = new ManagedThread(new Runnable() {
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(5 * 60 * 1000);
                    } catch (InterruptedException ignored) {
                    }
                    LOG.trace("Running GC");
                    System.gc();
                    LOG.trace("GC finished, running finalizers");
                    System.runFinalization();
                    LOG.trace("Finalizers finished.");
                }
            }
        }, "ManualGC");
        t.setDaemon(true);
        t.start();
        LOG.trace("Started manual GC thread.");
    }

    /**
     * Used to determine whether or not the backend threads have been
     * started.
     *
     * @return <tt>true</tt> if the backend threads have been started,
     *  otherwise <tt>false</tt>
     */
    public static boolean isStarted() {
        return _state >= 2;
    }

    /**
     * Returns the <tt>ActivityCallback</tt> passed to this' constructor.
    *
    * @return the <tt>ActivityCallback</tt> passed to this' constructor --
    *  this is one of the few accessors that can be <tt>null</tt> -- this 
    *  will be <tt>null</tt> in the case where the <tt>RouterService</tt>
    *  has not been constructed
     */
    public static ActivityCallback getCallback() {
        return RouterService.callback;
    }

    /**
     * Sets full power mode.
     */
    public static void setFullPower(boolean newValue) {
        if (_fullPower != newValue) {
            _fullPower = newValue;
            NormalUploadState.setThrottleSwitching(!newValue);
            HTTPDownloader.setThrottleSwitching(!newValue);
        }
    }

    /**
     * Accessor for the <tt>MessageRouter</tt> instance.
     *
     * @return the <tt>MessageRouter</tt> instance in use --
     *  this is one of the few accessors that can be <tt>null</tt> -- this 
     *  will be <tt>null</tt> in the case where the <tt>RouterService</tt>
     *  has not been constructed
     */
    public static MessageRouter getMessageRouter() {
        return router;
    }

    /**
     * Accessor for the <tt>FileManager</tt> instance in use.
     *
     * @return the <tt>FileManager</tt> in use
     */
    public static FileManager getFileManager() {
        return fileManager;
    }

    /** 
     * Accessor for the <tt>DownloadManager</tt> instance in use.
     *
     * @return the <tt>DownloadManager</tt> in use
     */
    public static DownloadManager getDownloadManager() {
        return downloader;
    }

    public static AltLocManager getAltlocManager() {
        return altManager;
    }

    public static ContentManager getContentManager() {
        return contentManager;
    }

    /**
     * Accessor for the <tt>UDPService</tt> instance.
     *
     * @return the <tt>UDPService</tt> instance in use
     */
    public static UDPService getUdpService() {
        return UDPSERVICE;
    }

    /**
     * Gets the UDPMultiplexor.
     */
    public static UDPMultiplexor getUDPConnectionManager() {
        return UDPMultiplexor.instance();
    }

    /**
     * Accessor for the <tt>ConnectionManager</tt> instance.
     *
     * @return the <tt>ConnectionManager</tt> instance in use
     */
    public static ConnectionManager getConnectionManager() {
        return manager;
    }

    /** 
     * Accessor for the <tt>UploadManager</tt> instance.
     *
     * @return the <tt>UploadManager</tt> in use
     */
    public static UploadManager getUploadManager() {
        return uploadManager;
    }

    /**
     * Accessor for the <tt>PushManager</tt> instance.
     *
     * @return the <tt>PushManager</tt> in use
     */
    public static PushManager getPushManager() {
        return pushManager;
    }

    /**
     * Accessor for the <tt>TorrentManager</tt> instance.
     * 
     * @return the <tt>TorrentManager</tt> we are using
     */
    public static TorrentManager getTorrentManager() {
        return torrentManager;
    }

    /** 
     * Accessor for the <tt>Acceptor</tt> instance.
     *
     * @return the <tt>Acceptor</tt> in use
     */
    public static Acceptor getAcceptor() {
        return acceptor;
    }

    /**
     * Accessor for the ConnectionDispatcher instance.
     */
    public static ConnectionDispatcher getConnectionDispatcher() {
        return dispatcher;
    }

    /** 
     * Accessor for the <tt>Acceptor</tt> instance.
     *
     * @return the <tt>Acceptor</tt> in use
     */
    public static HTTPAcceptor getHTTPAcceptor() {
        return httpAcceptor;
    }

    /** 
     * Accessor for the <tt>HostCatcher</tt> instance.
     *
     * @return the <tt>HostCatcher</tt> in use
     */
    public static HostCatcher getHostCatcher() {
        return catcher;
    }

    /** 
     * Accessor for the <tt>SearchResultHandler</tt> instance.
     *
     * @return the <tt>SearchResultHandler</tt> in use
     */
    public static SearchResultHandler getSearchResultHandler() {
        return RESULT_HANDLER;
    }

    /**
     * Accessor for the <tt>PromotionManager</tt> instance.
     * @return the <tt>PromotionManager</tt> in use.
     */
    public static PromotionManager getPromotionManager() {
        return promotionManager;
    }

    /** Gets the SecureMessageVerifier. */
    public static SecureMessageVerifier getSecureMessageVerifier() {
        return secureMessageVerifier;
    }

    public static byte[] getMyGUID() {
        return MYGUID;
    }

    /**
     * Schedules the given task for repeated fixed-delay execution on this's
     * backend thread.  <b>The task must not block for too long</b>, as 
     * a single thread is shared among all the backend.
     *
     * @param task the task to run repeatedly
     * @param delay the initial delay, in milliseconds
     * @param period the delay between executions, in milliseconds
     * @exception IllegalStateException this is cancelled
     * @exception IllegalArgumentException delay or period negative
     * @see com.limegroup.gnutella.util.SimpleTimer#schedule(java.lang.Runnable,long,long)
     */
    public static void schedule(Runnable task, long delay, long period) {
        timer.schedule(task, delay, period);
    }

    /**
     * Creates a new outgoing messaging connection to the given host and port.
     * Blocks until the connection established.  Throws IOException if
     * the connection failed.
     * @return a connection to the request host
     * @exception IOException the connection failed
     */
    public static ManagedConnection connectToHostBlocking(String hostname, int portnum) throws IOException {
        return manager.createConnectionBlocking(hostname, portnum);
    }

    /**
     * Creates a new outgoing messaging connection to the given host and port. 
     * Returns immediately without blocking.  If hostname would connect
     * us to ourselves, returns immediately.
     */
    public static void connectToHostAsynchronously(String hostname, int portnum) {
        //Don't allow connections to yourself.  We have to special
        //case connections to "localhost" or "127.0.0.1" since
        //they are aliases for this machine.

        byte[] cIP = null;
        InetAddress addr;
        try {
            addr = InetAddress.getByName(hostname);
            cIP = addr.getAddress();
        } catch (UnknownHostException e) {
            return;
        }
        if ((cIP[0] == 127) && (portnum == acceptor.getPort(true))
                && ConnectionSettings.LOCAL_IS_PRIVATE.getValue()) {
            return;
        } else {
            byte[] managerIP = acceptor.getAddress(true);
            if (Arrays.equals(cIP, managerIP) && portnum == acceptor.getPort(true))
                return;
        }

        if (!acceptor.isBannedIP(cIP)) {
            manager.createConnectionAsynchronously(hostname, portnum);
        }
    }

    /**
     * Determines if you're connected to the given host.
     */
    public static boolean isConnectedTo(InetAddress addr) {
        // ideally we would check download sockets too, but
        // because of the way ManagedDownloader is built, it isn't
        // too practical.
        // TODO: rewrite ManagedDownloader

        String host = addr.getHostAddress();
        return manager.isConnectedTo(host) || UDPMultiplexor.instance().isConnectedTo(addr)
                || uploadManager.isConnectedTo(addr); // ||
        // dloadManager.isConnectedTo(addr);
    }

    /**
     * Connects to the network.  Ensures the number of messaging connections
     * (keep-alive) is non-zero and recontacts the pong server as needed.  
     */
    public static void connect() {
        adjustSpamFilters();

        //delegate to connection manager
        manager.connect();
    }

    /**
     * Disconnects from the network.  Closes all connections and sets
     * the number of connections to zero.
     */
    public static void disconnect() {
        // Delegate to connection manager
        manager.disconnect();
    }

    /**
     * Closes and removes the given connection.
     */
    public static void removeConnection(ManagedConnection c) {
        manager.remove(c);
    }

    /**
     * Clears the hostcatcher.
     */
    public static void clearHostCatcher() {
        catcher.clear();
    }

    /**
     * Returns the number of pongs in the host catcher.  <i>This method is
     * poorly named, but it's obsolescent, so I won't bother to rename it.</i>
     */
    public static int getRealNumHosts() {
        return (catcher.getNumHosts());
    }

    /**
     * Returns the number of downloads in progress.
     */
    public static int getNumDownloads() {
        return downloader.downloadsInProgress();
    }

    /**
     * Returns the number of active downloads.
     */
    public static int getNumActiveDownloads() {
        return downloader.getNumActiveDownloads();
    }

    /**
     * Returns the number of downloads waiting to be started.
     */
    public static int getNumWaitingDownloads() {
        return downloader.getNumWaitingDownloads();
    }

    /**
     * Returns the number of individual downloaders.
     */
    public static int getNumIndividualDownloaders() {
        return downloader.getNumIndividualDownloaders();
    }

    /**
     * Returns the number of uploads in progress.
     */
    public static int getNumUploads() {
        return uploadManager.uploadsInProgress();
    }

    /**
     * Returns the number of queued uploads.
     */
    public static int getNumQueuedUploads() {
        return uploadManager.getNumQueuedUploads();
    }

    /**
     * Returns the current uptime.
     */
    public static long getCurrentUptime() {
        return STATISTICS.getUptime();
    }

    /**
     * Adds something that requires shutting down.
     *
     * TODO: Make this take a 'Service' or somesuch that
     *       has a shutdown method, and run the method in its
     *       own thread.
     */
    public static boolean addShutdownItem(Thread t) {
        if (isShuttingDown() || isShutdown())
            return false;

        SHUTDOWN_ITEMS.add(t);
        return true;
    }

    /**
     * Runs all shutdown items.
     */
    private static void runShutdownItems() {
        if (!isShuttingDown())
            return;

        // Start each shutdown item.
        for (Iterator i = SHUTDOWN_ITEMS.iterator(); i.hasNext();) {
            Thread t = (Thread) i.next();
            t.start();
        }

        // Now that we started them all, iterate back and wait for each one to finish.
        for (Iterator i = SHUTDOWN_ITEMS.iterator(); i.hasNext();) {
            Thread t = (Thread) i.next();
            try {
                t.join();
            } catch (InterruptedException ie) {
            }
        }
    }

    /**
     * Determines if this is shutting down.
     */
    private static boolean isShuttingDown() {
        return _state >= 3;
    }

    /**
     * Determines if this is shut down.
     */
    private static boolean isShutdown() {
        return _state >= 4;
    }

    /**
     * Shuts down the backend and writes the gnutella.net file.
     *
     * TODO: Make all of these things Shutdown Items.
     */
    public static synchronized void shutdown() {
        try {
            if (!isStarted())
                return;

            _state = 3;

            getAcceptor().shutdown();

            //Update fractional uptime statistics (before writing limewire.props)
            Statistics.instance().shutdown();

            // start closing all active torrents
            torrentManager.shutdown();

            //Update firewalled status
            ConnectionSettings.EVER_ACCEPTED_INCOMING.setValue(acceptedIncomingConnection());

            //Write gnutella.net
            try {
                catcher.write();
            } catch (IOException e) {
            }

            // save limewire.props & other settings
            SettingsHandler.save();

            RatingTable.instance().ageAndSave();

            cleanupPreviewFiles();

            downloader.writeSnapshot();

            torrentManager.writeSnapshot();

            fileManager.stop(); // Saves UrnCache and CreationTimeCache

            TigerTreeCache.instance().persistCache();

            LicenseFactory.persistCache();

            contentManager.shutdown();

            runShutdownItems();

            _state = 4;

        } catch (Throwable t) {
            ErrorService.error(t);
        }
    }

    public static void shutdown(String toExecute) {
        shutdown();
        if (toExecute != null) {
            try {
                Runtime.getRuntime().exec(toExecute);
            } catch (IOException tooBad) {
            }
        }
    }

    /**
     * Deletes all preview files.
     */
    private static void cleanupPreviewFiles() {
        //Cleanup any preview files.  Note that these will not be deleted if
        //your previewer is still open.
        File incompleteDir = SharingSettings.INCOMPLETE_DIRECTORY.getValue();
        if (incompleteDir == null)
            return; // if we could not get the incomplete directory, simply return.

        File[] files = incompleteDir.listFiles();
        if (files == null)
            return;

        for (int i = 0; i < files.length; i++) {
            String name = files[i].getName();
            if (name.startsWith(IncompleteFileManager.PREVIEW_PREFIX))
                files[i].delete(); //May or may not work; ignore return code.
        }
    }

    /**
     * Notifies the backend that spam filters settings have changed, and that
     * extra work must be done.
     */
    public static void adjustSpamFilters() {
        IPFilter.refreshIPFilter();

        //Just replace the spam filters.  No need to do anything
        //fancy like incrementally updating them.
        for (Iterator iter = manager.getConnections().iterator(); iter.hasNext();) {
            ManagedConnection c = (ManagedConnection) iter.next();
            c.setPersonalFilter(SpamFilter.newPersonalFilter());
            c.setRouteFilter(SpamFilter.newRouteFilter());
        }

        UDPReplyHandler.setPersonalFilter(SpamFilter.newPersonalFilter());
    }

    /**
     * Sets the port on which to listen for incoming connections.
     * If that fails, this is <i>not</i> modified and IOException is thrown.
     * If port==0, tells this to stop listening to incoming connections.
     */
    public static void setListeningPort(int port) throws IOException {
        acceptor.setListeningPort(port);
    }

    /** 
     * Returns true if this has accepted an incoming connection, and hence
     * probably isn't firewalled.  (This is useful for colorizing search
     * results in the GUI.)
     */
    public static boolean acceptedIncomingConnection() {
        return acceptor.acceptedIncoming();
    }

    /**
     * Count up all the messages on active connections
     */
    public static int getActiveConnectionMessages() {
        int count = 0;

        // Count the messages on initialized connections
        for (Iterator iter = manager.getInitializedConnections().iterator(); iter.hasNext();) {
            ManagedConnection c = (ManagedConnection) iter.next();
            count += c.getNumMessagesSent();
            count += c.getNumMessagesReceived();
        }
        return count;
    }

    /**
     * Count how many connections have already received N messages
     */
    public static int countConnectionsWithNMessages(int messageThreshold) {
        int count = 0;
        int msgs;

        // Count the messages on initialized connections
        for (Iterator iter = manager.getInitializedConnections().iterator(); iter.hasNext();) {
            ManagedConnection c = (ManagedConnection) iter.next();
            msgs = c.getNumMessagesSent();
            msgs += c.getNumMessagesReceived();
            if (msgs > messageThreshold)
                count++;
        }
        return count;
    }

    /**
     * Prints out the information about current initialied connections
     */
    public static void dumpConnections() {
        //dump ultrapeer connections
        System.out.println("UltraPeer connections");
        dumpConnections(manager.getInitializedConnections());
        //dump leaf connections
        System.out.println("Leaf connections");
        dumpConnections(manager.getInitializedClientConnections());
    }

    /**
     * Prints out the passed collection of connections
     * @param connections The collection(of Connection) 
     * of connections to be printed
     */
    private static void dumpConnections(Collection connections) {
        for (Iterator iterator = connections.iterator(); iterator.hasNext();) {
            System.out.println(iterator.next().toString());
        }
    }

    /** 
     * Returns a new GUID for passing to query.
     * This method is the central point of decision making for sending out OOB 
     * queries.
     */
    public static byte[] newQueryGUID() {
        if (isOOBCapable() && OutOfBandThroughputStat.isOOBEffectiveForMe())
            return GUID.makeAddressEncodedGuid(getAddress(), getPort());
        else
            return GUID.makeGuid();
    }

    /**
     * Searches the network for files of the given type with the given
     * GUID, query string and minimum speed.  If type is null, any file type
     * is acceptable.<p>
     *
     * ActivityCallback is notified asynchronously of responses.  These
     * responses can be matched with requests by looking at their GUIDs.  (You
     * may want to wrap the bytes with a GUID object for simplicity.)  An
     * earlier version of this method returned the reply GUID instead of taking
     * it as an argument.  Unfortunately this caused a race condition where
     * replies were returned before the GUI was prepared to handle them.
     * 
     * @param guid the guid to use for the query.  MUST be a 16-byte
     *  value as returned by newQueryGUID.
     * @param query the query string to use
     * @param minSpeed the minimum desired result speed
     * @param type the desired type of result (e.g., audio, video), or
     *  null if you don't care 
     */
    public static void query(byte[] guid, String query, MediaType type) {
        query(guid, query, "", type);
    }

    /** 
     * Searches the network for files with the given query string and 
     * minimum speed, i.e., same as query(guid, query, minSpeed, null). 
     *
     * @see query(byte[], String, MediaType)
     */
    public static void query(byte[] guid, String query) {
        query(guid, query, null);
    }

    /**
     * Searches the network for files with the given metadata.
     * 
     * @param richQuery metadata query to insert between the nulls,
     *  typically in XML format
     * @see query(byte[], String, MediaType)
     */
    public static void query(final byte[] guid, final String query, final String richQuery, final MediaType type) {

        try {
            QueryRequest qr = null;
            if (isIpPortValid() && (new GUID(guid)).addressesMatch(getAddress(), getPort())) {
                // if the guid is encoded with my address, mark it as needing out
                // of band support.  note that there is a VERY small chance that
                // the guid will be address encoded but not meant for out of band
                // delivery of results.  bad things may happen in this case but 
                // it seems tremendously unlikely, even over the course of a 
                // VERY long lived client
                qr = QueryRequest.createOutOfBandQuery(guid, query, richQuery, type);
                OutOfBandThroughputStat.OOB_QUERIES_SENT.incrementStat();
            } else
                qr = QueryRequest.createQuery(guid, query, richQuery, type);
            recordAndSendQuery(qr, type);
        } catch (Throwable t) {
            ErrorService.error(t);
        }
    }

    /**
     * Sends a 'What Is New' query on the network.
     */
    public static void queryWhatIsNew(final byte[] guid, final MediaType type) {
        try {
            QueryRequest qr = null;
            if (GUID.addressesMatch(guid, getAddress(), getPort())) {
                // if the guid is encoded with my address, mark it as needing out
                // of band support.  note that there is a VERY small chance that
                // the guid will be address encoded but not meant for out of band
                // delivery of results.  bad things may happen in this case but 
                // it seems tremendously unlikely, even over the course of a 
                // VERY long lived client
                qr = QueryRequest.createWhatIsNewOOBQuery(guid, (byte) 2, type);
                OutOfBandThroughputStat.OOB_QUERIES_SENT.incrementStat();
            } else
                qr = QueryRequest.createWhatIsNewQuery(guid, (byte) 2, type);

            if (FilterSettings.FILTER_WHATS_NEW_ADULT.getValue())
                MutableGUIDFilter.instance().addGUID(guid);

            recordAndSendQuery(qr, type);
        } catch (Throwable t) {
            ErrorService.error(t);
        }
    }

    /** Just aggregates some common code in query() and queryWhatIsNew().
     */
    private static void recordAndSendQuery(final QueryRequest qr, final MediaType type) {
        _lastQueryTime = System.currentTimeMillis();
        VERIFIER.record(qr, type);
        RESULT_HANDLER.addQuery(qr); // so we can leaf guide....
        router.sendDynamicQuery(qr);
    }

    /**
     * Accessor for the last time a query was originated from this host.
     *
     * @return a <tt>long</tt> representing the number of milliseconds since
     *  January 1, 1970, that the last query originated from this host
     */
    public static long getLastQueryTime() {
        return _lastQueryTime;
    }

    /** Purges the query from the QueryUnicaster (GUESS) and the ResultHandler
     *  (which maintains query stats for the purpose of leaf guidance).
     *  @param guid The GUID of the query you want to get rid of....
     */
    public static void stopQuery(GUID guid) {
        QueryUnicaster.instance().purgeQuery(guid);
        RESULT_HANDLER.removeQuery(guid);
        router.queryKilled(guid);
        if (RouterService.isSupernode())
            QueryDispatcher.instance().addToRemove(guid);
        MutableGUIDFilter.instance().removeGUID(guid.bytes());
    }

    /** 
     * Returns true if the given response is of the same type as the the query
     * with the given guid.  Returns 100 if guid is not recognized.
     *
     * @param guid the value returned by query(..).  MUST be 16 bytes long.
     * @param resp a response delivered by ActivityCallback.handleQueryReply
     * @see ResponseVerifier#matchesType(byte[], Response) 
     */
    public static boolean matchesType(byte[] guid, Response response) {
        return VERIFIER.matchesType(guid, response);
    }

    public static boolean matchesQuery(byte[] guid, Response response) {
        return VERIFIER.matchesQuery(guid, response);
    }

    /** 
     * Returns true if the given response for the query with the given guid is a
     * result of the Madragore worm (8KB files of form "x.exe").  Returns false
     * if guid is not recognized.  <i>Ideally this would be done by the normal
     * filtering mechanism, but it is not powerful enough without the query
     * string.</i>
     *
     * @param guid the value returned by query(..).  MUST be 16 byts long.
     * @param resp a response delivered by ActivityCallback.handleQueryReply
     * @see ResponseVerifier#isMandragoreWorm(byte[], Response) 
     */
    public static boolean isMandragoreWorm(byte[] guid, Response response) {
        return VERIFIER.isMandragoreWorm(guid, response);
    }

    /**
     * Returns a collection of IpPorts, preferencing hosts with open slots.
     * If isUltrapeer is true, this preferences hosts with open ultrapeer slots,
     * otherwise it preferences hosts with open leaf slots.
     *
     * Preferences via locale, also.
     * 
     * @param num How many endpoints to try to get
     */
    public static Collection getPreferencedHosts(boolean isUltrapeer, String locale, int num) {

        Set hosts = new IpPortSet();

        if (isUltrapeer)
            hosts.addAll(catcher.getUltrapeersWithFreeUltrapeerSlots(locale, num));
        else
            hosts.addAll(catcher.getUltrapeersWithFreeLeafSlots(locale, num));

        // If we don't have enough hosts, add more.

        if (hosts.size() < num) {
            //we first try to get the connections that match the locale.
            List conns = manager.getInitializedConnectionsMatchLocale(locale);
            for (Iterator i = conns.iterator(); i.hasNext() && hosts.size() < num;)
                hosts.add(i.next());

            //if we still don't have enough hosts, get them from the list
            //of all initialized connection
            if (hosts.size() < num) {
                //list returned is unmmodifiable
                conns = manager.getInitializedConnections();
                for (Iterator i = conns.iterator(); i.hasNext() && hosts.size() < num;)
                    hosts.add(i.next());
            }
        }

        return hosts;
    }

    /**
     *  Returns the number of messaging connections.
     */
    public static int getNumConnections() {
        return manager.getNumConnections();
    }

    /**
     *  Returns the number of initialized messaging connections.
     */
    public static int getNumInitializedConnections() {
        return manager.getNumInitializedConnections();
    }

    /**
     * Returns the number of active ultrapeer -> leaf connections.
     */
    public static int getNumUltrapeerToLeafConnections() {
        return manager.getNumInitializedClientConnections();
    }

    /**
     * Returns the number of leaf -> ultrapeer connections.
     */
    public static int getNumLeafToUltrapeerConnections() {
        return manager.getNumClientSupernodeConnections();
    }

    /**
     * Returns the number of ultrapeer -> ultrapeer connections.
     */
    public static int getNumUltrapeerToUltrapeerConnections() {
        return manager.getNumUltrapeerConnections();
    }

    /**
     * Returns the number of old unrouted connections.
     */
    public static int getNumOldConnections() {
        return manager.getNumOldConnections();
    }

    /**
     * Returns whether or not this client currently has any initialized 
     * connections.
     *
     * @return <tt>true</tt> if the client does have initialized connections,
     *  <tt>false</tt> otherwise
     */
    public static boolean isFullyConnected() {
        return manager.isFullyConnected();
    }

    /**
     * Returns whether or not this client currently has any initialized 
     * connections.
     *
     * @return <tt>true</tt> if the client does have initialized connections,
     *  <tt>false</tt> otherwise
     */
    public static boolean isConnected() {
        return manager.isConnected();
    }

    /**
     * Returns whether or not this client is attempting to connect.
     */
    public static boolean isConnecting() {
        return manager.isConnecting();
    }

    /**
     * Returns whether or not this client is currently fetching
     * endpoints from a GWebCache.
     *
     * @return <tt>true</tt> if the client is fetching endpoints.
     */
    public static boolean isFetchingEndpoints() {
        return BootstrapServerManager.instance().isEndpointFetchInProgress();
    }

    /**
     * Returns the number of files being shared locally.
     */
    public static int getNumSharedFiles() {
        return (fileManager.getNumFiles());
    }

    /**
     * Returns the number of files which are awaiting sharing.
     */
    public static int getNumPendingShared() {
        return (fileManager.getNumPendingFiles());
    }

    /**
     * Returns the size in bytes of shared files.
     *
     * @return the size in bytes of shared files on this host
     */
    public static int getSharedFileSize() {
        return fileManager.getSize();
    }

    /** 
     * Returns a list of all incomplete shared file descriptors.
     */
    public static FileDesc[] getIncompleteFileDescriptors() {
        return fileManager.getIncompleteFileDescriptors();
    }

    /**
     * Returns a list of all shared file descriptors in the given directory.
     * All the file descriptors returned have already been passed to the gui
     * via ActivityCallback.addSharedFile.  Note that if a file descriptor
     * is added to the given directory after this method completes, 
     * addSharedFile will be called for that file descriptor.<p>
     *
     * If directory is not a shared directory, returns null.
     */
    public static FileDesc[] getSharedFileDescriptors(File directory) {
        return fileManager.getSharedFileDescriptors(directory);
    }

    /** 
     * Tries to "smart download" <b>any</b> [sic] of the given files.<p>  
     *
     * If any of the files already being downloaded (or queued for downloaded)
     * has the same temporary name as any of the files in 'files', throws
     * SaveLocationException.  Note, however, that this doesn't guarantee
     * that a successfully downloaded file can be moved to the library.<p>
     *
     * If overwrite==false, then if any of the files already exists in the
     * download directory, SaveLocationException is thrown and no files are
     * modified.  If overwrite==true, the files may be overwritten.<p>
     * 
     * Otherwise returns a Downloader that allows you to stop and resume this
     * download.  The ActivityCallback will also be notified of this download,
     * so the return value can usually be ignored.  The download begins
     * immediately, unless it is queued.  It stops after any of the files
     * succeeds.  
     *
     * @param files a group of "similar" files to smart download
     * @param alts a List of secondary RFDs to use for other sources
     * @param queryGUID guid of the query that returned the results (i.e. files)
    * @param overwrite true iff the download should proceedewithout
     *  checking if it's on disk
    * @param saveDir can be null, then the save directory from the settings
    * is used
    * @param fileName can be null, then one of the filenames of the 
    * <code>files</code> array is used
    * array is used
     * @return the download object you can use to start and resume the download
     * @throws SaveLocationException if there is an error when setting the final
     * file location of the download 
     * @see DownloadManager#getFiles(RemoteFileDesc[], boolean)
     */
    public static Downloader download(RemoteFileDesc[] files, List alts, GUID queryGUID, boolean overwrite,
            File saveDir, String fileName) throws SaveLocationException {
        return downloader.download(files, alts, queryGUID, overwrite, saveDir, fileName);
    }

    public static Downloader download(RemoteFileDesc[] files, List alts, GUID queryGUID, boolean overwrite)
            throws SaveLocationException {
        return download(files, alts, queryGUID, overwrite, null, null);
    }

    /**
     * Stub for calling download(RemoteFileDesc[], DataUtils.EMPTY_LIST, boolean)
     * @throws SaveLocationException 
     */
    public static Downloader download(RemoteFileDesc[] files, GUID queryGUID, boolean overwrite, File saveDir,
            String fileName) throws SaveLocationException {
        return download(files, Collections.EMPTY_LIST, queryGUID, overwrite, saveDir, fileName);
    }

    public static Downloader download(RemoteFileDesc[] files, boolean overwrite, GUID queryGUID)
            throws SaveLocationException {
        return download(files, queryGUID, overwrite, null, null);
    }

    /**
     * Creates a downloader for a magnet.
     * @param magnetprovides the information of the  file to download, must be
     *  valid
     * @param overwrite whether an existing file a the final file location 
     * should be overwritten
     * @return
     * @throws SaveLocationException
     * @throws IllegalArgumentException if the magnet is not 
     * {@link MagnetOptions#isDownloadable() valid}.
     */
    public static Downloader download(MagnetOptions magnet, boolean overwrite) throws SaveLocationException {
        if (!magnet.isDownloadable()) {
            throw new IllegalArgumentException("invalid magnet: not have enough information for downloading");
        }
        return downloader.download(magnet, overwrite, null, magnet.getDisplayName());
    }

    /**
     * Creates a downloader for a magnet using the given additional options.
     *
     * @param magnet provides the information of the  file to download, must be
     *  valid
     * @param overwrite whether an existing file a the final file location 
     * should be overwritten
     * @param saveDir can be null, then the save directory from the settings
     * is used
     * @param fileName the final filename of the download, can be
     * <code>null</code>
     * @return
     * @throws SaveLocationException
     * @throws IllegalArgumentException if the magnet is not
     * {@link MagnetOptions#isDownloadable() downloadable}.
     */
    public static Downloader download(MagnetOptions magnet, boolean overwrite, File saveDir, String fileName)
            throws SaveLocationException {
        return downloader.download(magnet, overwrite, saveDir, fileName);
    }

    /**
      * Starts a resume download for the given incomplete file.
      * @exception CantResumeException incompleteFile is not a valid 
      *  incomplete file
      * @throws SaveLocationException 
      */
    public static Downloader download(File incompleteFile) throws CantResumeException, SaveLocationException {
        return downloader.download(incompleteFile);
    }

    /**
    * Starts a torrent download for a given Inputstream to the .torrent file
    * 
    * @param is
    *            the InputStream belonging to the .torrent file
    * @throws IOException
    *             in case there was a problem reading the file 
    */
    public static Downloader downloadTorrent(File torrentFile) throws IOException {
        return torrentManager.download(torrentFile);
    }

    /**
    * Starts a torrent download for a given Inputstream to the .torrent file
    * 
    * @param url
    *            the url, where the .torrent file is located
    * @throws IOException
    *             in case there was a problem downloading the .torrent
    */
    public static Downloader downloadTorrent(URL url) throws IOException {
        return torrentManager.download(url);
    }

    /**
     * Creates and returns a new chat to the given host and port.
     */
    public static Chatter createChat(String host, int port) {
        Chatter chatter = ChatManager.instance().request(host, port);
        return chatter;
    }

    /**
    * Browses the passed host
     * @param host The host to browse
     * @param port The port at which to browse
     * @param guid The guid to be used for the query replies received 
     * while browsing host
     * @param serventID The guid of the client to browse from.  I need this in
     * case I need to push....
     * @param proxies the list of PushProxies we can use - may be null.
     * @param canDoFWTransfer true if the remote host supports fw transfer
    */
    public static BrowseHostHandler doAsynchronousBrowseHost(final String host, final int port, GUID guid,
            GUID serventID, final Set proxies, final boolean canDoFWTransfer) {
        final BrowseHostHandler handler = new BrowseHostHandler(callback, guid, serventID);
        ThreadFactory.startThread(new Runnable() {
            public void run() {
                handler.browseHost(host, port, proxies, canDoFWTransfer);
            }
        }, "BrowseHoster");

        return handler;
    }

    /**
     * Tells whether the node is a supernode or not
     * @return true, if supernode, false otherwise
     */
    public static boolean isSupernode() {
        return manager.isSupernode();
    }

    /**
     * Accessor for whether or not this node is a shielded leaf.
     *
     * @return <tt>true</tt> if this node is a shielded leaf, 
     *  <tt>false</tt> otherwise
     */
    public static boolean isShieldedLeaf() {
        return manager.isShieldedLeaf();
    }

    /**
     * @return the number of free leaf slots.
     */
    public static int getNumFreeLeafSlots() {
        return manager.getNumFreeLeafSlots();
    }

    /**
     * @return the number of free non-leaf slots.
     */
    public static int getNumFreeNonLeafSlots() {
        return manager.getNumFreeNonLeafSlots();
    }

    /**
     * @return the number of free leaf slots available for limewires.
     */
    public static int getNumFreeLimeWireLeafSlots() {
        return manager.getNumFreeLimeWireLeafSlots();
    }

    /**
     * @return the number of free non-leaf slots available for limewires.
     */
    public static int getNumFreeLimeWireNonLeafSlots() {
        return manager.getNumFreeLimeWireNonLeafSlots();
    }

    /**
     * Sets the flag for whether or not LimeWire is currently in the process of 
    * shutting down.
    *
     * @param flag the shutting down state to set
     */
    public static void setIsShuttingDown(boolean flag) {
        isShuttingDown = flag;
    }

    /**
     * Returns whether or not LimeWire is currently in the shutting down state,
     * meaning that a shutdown has been initiated but not completed.  This
     * is most often the case when there are active file transfers and the
     * application is set to shutdown after current file transfers are complete.
     *
     * @return <tt>true</tt> if the application is in the shutting down state,
     *  <tt>false</tt> otherwise
     */
    public static boolean getIsShuttingDown() {
        return isShuttingDown;
    }

    /**
     * Notifies components that this' IP address has changed.
     */
    public static boolean addressChanged() {
        if (callback != null)
            callback.addressStateChanged();

        // Only continue if the current address/port is valid & not private.
        byte addr[] = getAddress();
        int port = getPort();
        if (!NetworkUtils.isValidAddress(addr))
            return false;
        if (NetworkUtils.isPrivateAddress(addr))
            return false;
        if (!NetworkUtils.isValidPort(port))
            return false;

        // reset the last connect back time so the next time the TCP/UDP
        // validators run they try to connect back.
        if (acceptor != null)
            acceptor.resetLastConnectBackTime();
        if (UDPSERVICE != null)
            UDPSERVICE.resetLastConnectBackTime();

        if (manager != null) {
            Properties props = new Properties();
            props.put(HeaderNames.LISTEN_IP, NetworkUtils.ip2string(addr) + ":" + port);
            HeaderUpdateVendorMessage huvm = new HeaderUpdateVendorMessage(props);

            for (Iterator iter = manager.getInitializedConnections().iterator(); iter.hasNext();) {
                ManagedConnection c = (ManagedConnection) iter.next();
                if (c.remoteHostSupportsHeaderUpdate() >= HeaderUpdateVendorMessage.VERSION)
                    c.send(huvm);
            }

            for (Iterator iter = manager.getInitializedClientConnections().iterator(); iter.hasNext();) {
                ManagedConnection c = (ManagedConnection) iter.next();
                if (c.remoteHostSupportsHeaderUpdate() >= HeaderUpdateVendorMessage.VERSION)
                    c.send(huvm);
            }
        }
        return true;
    }

    /**
     * Notification that we've either just set or unset acceptedIncoming.
     */
    public static boolean incomingStatusChanged() {
        if (callback != null)
            callback.addressStateChanged();

        // Only continue if the current address/port is valid & not private.
        byte addr[] = getAddress();
        int port = getPort();
        if (!NetworkUtils.isValidAddress(addr))
            return false;
        if (NetworkUtils.isPrivateAddress(addr))
            return false;
        if (!NetworkUtils.isValidPort(port))
            return false;

        return true;
    }

    /**
     * Returns the external IP address for this host.
     */
    public static byte[] getExternalAddress() {
        return acceptor.getExternalAddress();
    }

    /**
     * Returns the raw IP address for this host.
     *
     * @return the raw IP address for this host
     */
    public static byte[] getAddress() {
        return acceptor.getAddress(true);
    }

    /**
     * Returns the Non-Forced IP address for this host.
     *
     * @return the non-forced IP address for this host
     */
    public static byte[] getNonForcedAddress() {
        return acceptor.getAddress(false);
    }

    /**
     * Returns the port used for downloads and messaging connections.
     * Used to fill out the My-Address header in ManagedConnection.
     * @see Acceptor#getPort
     */
    public static int getPort() {
        return acceptor.getPort(true);
    }

    /**
    * Returns the Non-Forced port for this host.
    *
    * @return the non-forced port for this host
    */
    public static int getNonForcedPort() {
        return acceptor.getPort(false);
    }

    /**
     * Returns whether or not this node is capable of sending its own
     * GUESS queries.  This would not be the case only if this node
     * has not successfully received an incoming UDP packet.
     *
     * @return <tt>true</tt> if this node is capable of running its own
     *  GUESS queries, <tt>false</tt> otherwise
     */
    public static boolean isGUESSCapable() {
        return UDPSERVICE.isGUESSCapable();
    }

    /** 
     * Returns whether or not this node is capable of performing OOB queries.
     */
    public static boolean isOOBCapable() {
        return isGUESSCapable() && OutOfBandThroughputStat.isSuccessRateGood() && !NetworkUtils.isPrivate()
                && SearchSettings.OOB_ENABLED.getValue() && acceptor.isAddressExternal() && isIpPortValid();
    }

    public static GUID getUDPConnectBackGUID() {
        return UDPSERVICE.getConnectBackGUID();
    }

    /** @return true if your IP and port information is valid.
     */
    public static boolean isIpPortValid() {
        return (NetworkUtils.isValidAddress(getAddress()) && NetworkUtils.isValidPort(getPort()));
    }

    public static boolean canReceiveSolicited() {
        return UDPSERVICE.canReceiveSolicited();
    }

    public static boolean canReceiveUnsolicited() {
        return UDPSERVICE.canReceiveUnsolicited();
    }

    public static boolean canDoFWT() {
        return UDPSERVICE.canDoFWT();
    }
}