package com.limegroup.gnutella; import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.LinkedList; import java.util.Properties; import java.util.Set; import java.util.TreeSet; import com.util.LOG; import com.limegroup.gnutella.bootstrap.BootstrapServerManager; import com.limegroup.gnutella.chat.ChatManager; import com.limegroup.gnutella.chat.Chatter; import com.limegroup.gnutella.downloader.AlreadyDownloadingException; import com.limegroup.gnutella.downloader.CantResumeException; import com.limegroup.gnutella.downloader.FileExistsException; 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.messages.QueryRequest; import com.limegroup.gnutella.messages.vendor.HeaderUpdateVendorMessage; import com.limegroup.gnutella.search.SearchResultHandler; import com.limegroup.gnutella.security.Authenticator; import com.limegroup.gnutella.security.Cookies; import com.limegroup.gnutella.security.ServerAuthenticator; import com.limegroup.gnutella.settings.ApplicationSettings; import com.limegroup.gnutella.settings.ConnectionSettings; import com.limegroup.gnutella.settings.SearchSettings; import com.limegroup.gnutella.settings.SharingSettings; import com.limegroup.gnutella.udpconnect.UDPMultiplexor; import com.limegroup.gnutella.util.ManagedThread; import com.limegroup.gnutella.util.NetworkUtils; import com.limegroup.gnutella.util.SimpleTimer; import com.limegroup.gnutella.util.IpPort; /** * 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 { /** * For authenticating users. */ private static final Authenticator authenticator = new ServerAuthenticator(); /** * 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(); /** * Initialize the class that manages all TCP connections. */ private static ConnectionManager manager = new ConnectionManager(authenticator); /** * <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(); private static final ResponseVerifier verifier = new ResponseVerifier(); /** * 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(); /** * 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 _started; /** * 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; public static final byte [] MYGUID = GUID.makeGuid(); /** * 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()); } /** * 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; RouterService.router = router; } public synchronized void setCallback(ActivityCallback callback) { RouterService.callback = callback; } /** * Performs startup tasks that should happen while the GUI loads */ public static void asyncGuiInit() { synchronized(RouterService.class) { if (_started > 0) // already did this? return; else _started = 1; } Thread t = new ManagedThread(new Initializer()); t.setName("async gui initializer"); t.setDaemon(true); t.start(); } /** * 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 (_started > 0) // already did this? return; else _started = 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(); _started = 2; // Now, link all the pieces together, starting the various threads. 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"); // 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"); // Make sure connections come up ultra-fast (beyond default keepAlive) LOG.trace("START connect"); connect(); LOG.trace("STOP connect"); // Asynchronously load files now that the GUI is up, notifying // callback. // Restore any downloads in progress. LOG.trace("START DownloadManager.postGuiInit"); callback.componentLoading("DOWNLOAD_MANAGER_POST_GUI"); downloader.postGuiInit(); LOG.trace("STOP DownloadManager.postGuiInit"); } } /** * 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 _started >= 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; } } /** * 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>DownloadManager</tt> instance in use. * * @return the <tt>DownloadManager</tt> in use */ public static DownloadManager getDownloadManager() { return downloader; } /** * 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>Acceptor</tt> instance. * * @return the <tt>Acceptor</tt> in use */ public static Acceptor getAcceptor() { return acceptor; } /** * 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; } public static byte [] getMyGUID() { return MYGUID; } /** * Schedules the given task for repeated fixed-delay execution on this' * 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())) { return; } else { byte[] managerIP=acceptor.getAddress(); if (Arrays.equals(cIP, managerIP) && portnum==acceptor.getPort()) 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); // 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(); } public static void restart() { disconnect(); 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(); } /** * 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 _started >= 3; } /** * Determines if this is shut down. */ private static boolean isShutdown() { return _started >= 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; _started = 3; //Update firewalled status ConnectionSettings.EVER_ACCEPTED_INCOMING.setValue(acceptedIncomingConnection()); //Write gnutella.net try { catcher.write(); } catch (IOException e) {} cleanupPreviewFiles(); downloader.writeSnapshot(); Cookies.instance().save(); runShutdownItems(); _started = 4; } catch(Throwable t) { ErrorService.error(t); } } /** * 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; 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; } /** * Returns the number of good hosts in my horizon. */ public static long getNumHosts() { return HorizonCounter.instance().getNumHosts(); } /** * Returns the number of files in my horizon. */ public static long getNumFiles() { return HorizonCounter.instance().getNumFiles(); } /** * Returns the size of all files in my horizon, in kilobytes. */ public static long getTotalFileSize() { return HorizonCounter.instance().getTotalFileSize(); } /** * Prints out the information about current initialied connections */ public static void dumpConnections() { //dump ultrapeer connections LOG.info("UltraPeer connections"); dumpConnections(manager.getInitializedConnections()); //dump leaf connections LOG.info("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();) { LOG.info(iterator.next().toString()); } } /** * Updates the horizon statistics. This should called at least every five * minutes or so to prevent the reported numbers from growing too large. * You can safely call it more often. Note that it does not modify the * network; horizon stats are calculated by passively looking at messages. * * @modifies this (values returned by getNumFiles, getTotalFileSize, and * getNumHosts) */ public static void updateHorizon() { HorizonCounter.instance().refresh(); } /** * 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()) 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); } else qr = QueryRequest.createQuery(guid, query, richQuery, type); 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); } // TODO: comment out public static void resendWhenConnect(ManagedConnection mc) { /* QueryRequest qr = RESULT_HANDLER.getCurQuery(); LOG.logxml("resendWhenConnect " + (qr != null)); if (qr != null) { router.resendQuery(mc, 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) { RESULT_HANDLER.removeQuery(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); } /** * 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) { // note that we need to use a TreeSet because the objects returned // from the various adding calls below will be different types, // and hashCode & equals won't be respected. Set hosts = new TreeSet(IpPort.COMPARATOR); 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 leaf -> ultrapeer connections. */ public static int getNumLeafToUltrapeerConnections() { return manager.getNumClientSupernodeConnections(); } /** * 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(); } /** * 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 * AlreadyDownloadingException. 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, FileExistsException 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 overwrite true iff the download should proceded without * checking if it's on disk * @param the guid of the query that returned the results (i.e. files) * @return the download object you can use to start and resume the download * @exception AlreadyDownloadingException the file is already being * downloaded. * @exception FileExistsException the file already exists in the library * @see DownloadManager#getFiles(RemoteFileDesc[], boolean) */ public static Downloader download(RemoteFileDesc[] files, Set<Endpoint> alts, boolean overwrite, GUID queryGUID) throws FileExistsException, AlreadyDownloadingException, java.io.FileNotFoundException { return downloader.download(files, alts, overwrite, queryGUID); } /** * Stub for calling download(RemoteFileDesc[], DataUtils.EMPTY_LIST, boolean) */ public static Downloader download(RemoteFileDesc[] files, boolean overwrite, GUID queryGUID) throws FileExistsException, AlreadyDownloadingException, java.io.FileNotFoundException { return download(files, Collections.EMPTY_SET, overwrite, queryGUID); } /* * Creates a new MAGNET downloader. Immediately tries to download from * <tt>defaultURL</tt>, if specified. If that fails, or if defaultURL does * not provide alternate locations, issues a requery with <tt>textQuery</tt> * and </tt>urn</tt>, as provided. (At least one must be non-null.) If * <tt>filename</tt> is specified, it will be used as the name of the * complete file; otherwise it will be taken from any search results or * guessed from <tt>defaultURL</tt>. * * @param urn the hash of the file (exact topic), or null if unknown * @param textQuery requery keywords (keyword topic), or null if unknown * @param filename the final file name, or null if unknown * @param defaultURLs the initial locations to try (exact source), or null * if unknown * * @exception AlreadyDownloadingException couldn't download because the * another downloader is getting the file * @exception IllegalArgumentException both urn and textQuery are null */ public static synchronized Downloader download(URN urn, String textQuery, String filename, String [] defaultURL, boolean overwrite) throws IllegalArgumentException, AlreadyDownloadingException, FileExistsException { return downloader.download(urn,textQuery,filename,defaultURL,overwrite); } /** * Starts a resume download for the given incomplete file. * @exception AlreadyDownloadingException couldn't download because the * another downloader is getting the file * @exception CantResumeException incompleteFile is not a valid * incomplete file */ public static Downloader download(File incompleteFile) throws AlreadyDownloadingException, CantResumeException { return downloader.download(incompleteFile); } /** * 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); Thread asynch = new ManagedThread( new Runnable() { public void run() { try { handler.browseHost(host, port, proxies, canDoFWTransfer); } catch(Throwable t) { ErrorService.error(t); } } }, "BrowseHoster" ); asynch.setDaemon(true); asynch.start(); return handler; } /** * @return the number of free non-leaf slots. */ public static int getNumFreeNonLeafSlots() { return manager.getNumFreeNonLeafSlots(); } /** * 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() >= huvm.VERSION) c.send(huvm); } for (Iterator iter = manager.getInitializedClientConnections().iterator();iter.hasNext();) { ManagedConnection c = (ManagedConnection)iter.next(); if (c.remoteHostSupportsHeaderUpdate() >= huvm.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(); } /** * 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(); } /** * 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(); } /** * Returns the Non-Forced port for this host. * * @return the non-forced port for this host */ public static int getNonForcedPort() { return acceptor.getPort(); } /** * 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() && !NetworkUtils.isPrivate() && SearchSettings.OOB_ENABLED && 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(); } }