package com.limegroup.gnutella.routing; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.limewire.core.settings.SearchSettings; import org.limewire.core.settings.SharingSettings; import org.limewire.inject.EagerSingleton; import org.limewire.inspection.Inspectable; import org.limewire.lifecycle.Service; import org.limewire.listener.EventListener; import org.limewire.listener.ListenerSupport; import org.limewire.logging.Log; import org.limewire.logging.LogFactory; import org.limewire.setting.evt.SettingEvent; import org.limewire.setting.evt.SettingListener; import com.google.inject.Inject; import com.google.inject.name.Named; import com.limegroup.gnutella.library.FileDesc; import com.limegroup.gnutella.library.FileDescChangeEvent; import com.limegroup.gnutella.library.FileView; import com.limegroup.gnutella.library.FileViewChangeEvent; import com.limegroup.gnutella.library.GnutellaFiles; import com.limegroup.gnutella.library.IncompleteFileDesc; import com.limegroup.gnutella.library.IncompleteFiles; import com.limegroup.gnutella.util.LimeWireUtils; import com.limegroup.gnutella.xml.LimeXMLDocument; /** * Updates the QueryRouteTable. Listens for changes to shared files in the * FileManager. When changes occur, a new QRT will lazily be rebuilt. */ @EagerSingleton public class QRPUpdater implements SettingListener, Service, Inspectable { private static Log LOG = LogFactory.getLog(QRPUpdater.class); /** * delay between qrp updates should the simpp words change. * Not final for testing. Betas update faster for experiments. */ private static long QRP_DELAY = (LimeWireUtils.isBetaRelease() ? 1 : 60) * 60 * 1000; private final FileView gnutellaFileView; private final FileView incompleteFileView; private final ScheduledExecutorService backgroundExecutor; private final ListenerSupport<FileDescChangeEvent> fileDescListenerSupport; /** * Schedules a delayed rebuild task when simpp changes occur */ private ScheduledFuture<?> scheduledSimppRebuildTimer; /** * Holds references to all the entries in the current QRP. These are saved * to compare against any SIMPP messages to prevent unnecissary rebuilds. */ private final Set<String> qrpWords = new HashSet<String>(); /** * Boolean for checking if the QRT needs to be rebuilt. */ private boolean needRebuild = true; /** * The QueryRouteTable kept by this. The QueryRouteTable will be * lazily rebuilt when necessary. */ private QueryRouteTable queryRouteTable; @Inject public QRPUpdater( @Named("backgroundExecutor") ScheduledExecutorService backgroundExecutor, ListenerSupport<FileDescChangeEvent> fileDescListenerSupport, @GnutellaFiles FileView gnutellaFileView, @IncompleteFiles FileView incompleteFileView) { this.backgroundExecutor = backgroundExecutor; this.fileDescListenerSupport = fileDescListenerSupport; this.gnutellaFileView = gnutellaFileView; this.incompleteFileView = incompleteFileView; for (String entry : SearchSettings.LIME_QRP_ENTRIES.get()) qrpWords.add(entry); } public synchronized void settingChanged(SettingEvent evt) { // return immediately if we aren't publishing lime keywords if (!SearchSettings.PUBLISH_LIME_KEYWORDS.getBoolean()) return; Set<String> newWords = new HashSet<String>(); for (String entry : SearchSettings.LIME_QRP_ENTRIES.get()) newWords.add(entry); // any change in words? if (newWords.containsAll(qrpWords) && qrpWords.containsAll(newWords)) { return; } qrpWords.clear(); qrpWords.addAll(newWords); // if its already schedule to be rebuilt or a build is already needed return if( needRebuild || (scheduledSimppRebuildTimer != null && !scheduledSimppRebuildTimer.isDone())) { return; } // schedule a rebuild sometime in the next hour scheduledSimppRebuildTimer = backgroundExecutor.schedule(new Runnable() { public void run() { needRebuild = true; } }, (int) (Math.random() * QRP_DELAY), TimeUnit.MICROSECONDS); } /** * Returns a new QueryRouteTable. If the QRT is stale, will rebuild the * QRT prior to returning a new QueryRouteTable. */ public synchronized QueryRouteTable getQRT() { LOG.debug("getQRT"); if (needRebuild) { if(scheduledSimppRebuildTimer != null ) scheduledSimppRebuildTimer.cancel(true); buildQRT(); needRebuild = false; } QueryRouteTable qrt = new QueryRouteTable(queryRouteTable.getSize()); qrt.addAll(queryRouteTable); return qrt; } /** * Build the qrt. */ private void buildQRT() { LOG.debug("building QRT"); queryRouteTable = new QueryRouteTable(); if (SearchSettings.PUBLISH_LIME_KEYWORDS.getBoolean()) { for (String entry : SearchSettings.LIME_QRP_ENTRIES.get()) { queryRouteTable.addIndivisible(entry); } } gnutellaFileView.getReadLock().lock(); try { for (FileDesc fd : gnutellaFileView) { queryRouteTable.add(fd.getFileName()); for(LimeXMLDocument doc : fd.getLimeXMLDocuments()) { for(String word : doc.getKeyWords()) { queryRouteTable.add(word); } for(String word : doc.getKeyWordsIndivisible()) { queryRouteTable.addIndivisible(word); } // also add schema uri needed by rich queries String schemaURI = doc.getSchemaURI(); queryRouteTable.addIndivisible(schemaURI); } } } finally { gnutellaFileView.getReadLock().unlock(); } //if partial sharing is allowed, add incomplete file keywords also if(SharingSettings.ALLOW_PARTIAL_SHARING.getValue() && SharingSettings.PUBLISH_PARTIAL_QRP.getValue()) { incompleteFileView.getReadLock().lock(); try { for(FileDesc fd : incompleteFileView) { IncompleteFileDesc ifd = (IncompleteFileDesc) fd; if (ifd.hasUrnsAndPartialData()) { queryRouteTable.add(ifd.getFileName()); } } } finally { incompleteFileView.getReadLock().unlock(); } } } @Inject void register(org.limewire.lifecycle.ServiceRegistry registry) { registry.register(this); } public String getServiceName() { return org.limewire.i18n.I18nMarker.marktr("QRP Updater"); } public void initialize() { SearchSettings.PUBLISH_LIME_KEYWORDS.addSettingListener(this); SearchSettings.LIME_QRP_ENTRIES.addSettingListener(this); gnutellaFileView.addListener(new EventListener<FileViewChangeEvent>() { @Override public void handleEvent(FileViewChangeEvent event) { switch(event.getType()) { case FILE_ADDED: case FILE_REMOVED: case FILE_META_CHANGED: case FILE_CHANGED: case FILES_CLEARED: needRebuild = true; break; } } }); incompleteFileView.addListener(new EventListener<FileViewChangeEvent>() { @Override public void handleEvent(FileViewChangeEvent event) { switch(event.getType()) { case FILE_ADDED: case FILE_REMOVED: case FILES_CLEARED: needRebuild = true; break; } } }); fileDescListenerSupport.addListener(new EventListener<FileDescChangeEvent>() { @Override public void handleEvent(FileDescChangeEvent event) { switch(event.getType()) { case TT_ROOT_ADDED: needRebuild = true; break; } } }); } public void start() {} public void stop() { SearchSettings.PUBLISH_LIME_KEYWORDS.removeSettingListener(this); SearchSettings.LIME_QRP_ENTRIES.removeSettingListener(this); } @Override public Object inspect() { Map<String, Object> ret = new HashMap<String, Object>(); synchronized(this) { ret.put("qrt",getQRT().getRawDump()); } return ret; } }