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(); } }