package com.limegroup.gnutella.dht.db; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.limewire.collection.MultiCollection; import org.limewire.core.settings.DHTSettings; import org.limewire.mojito.KUID; import org.limewire.mojito.db.DHTValue; import org.limewire.mojito.db.Storable; import org.limewire.mojito.db.StorableModel; import org.limewire.mojito.result.StoreResult; import org.limewire.mojito.util.CollectionUtils; import org.limewire.mojito.util.DatabaseUtils; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; import com.limegroup.gnutella.URN; import com.limegroup.gnutella.dht.util.KUIDUtils; import com.limegroup.gnutella.library.FileDesc; import com.limegroup.gnutella.library.FileView; import com.limegroup.gnutella.library.GnutellaFiles; import com.limegroup.gnutella.tigertree.HashTree; import com.limegroup.gnutella.tigertree.HashTreeCache; /** * Publishes the localhost as an alternate * locations for rare files. Rare files are files that haven't * been uploaded for a certain amount of time. There are various * other ways to determinate the rareness of a file like using * the number of query hits instead of upload attempts or even * keeping track of the file activities over multiple sessions. */ @Singleton public class AltLocModel implements StorableModel { private final Map<KUID, Storable> values = Collections.synchronizedMap(new HashMap<KUID, Storable>()); private final AltLocValueFactory altLocValueFactory; private final Provider<HashTreeCache> tigerTreeCache; private final FileView gnutellaFileView; @Inject public AltLocModel(AltLocValueFactory altLocValueFactory, @GnutellaFiles FileView gnutellaFileView, Provider<HashTreeCache> tigerTreeCache) { this.altLocValueFactory = altLocValueFactory; this.gnutellaFileView = gnutellaFileView; this.tigerTreeCache = tigerTreeCache; } public Collection<Storable> getStorables() { if (!DHTSettings.PUBLISH_ALT_LOCS.getValue()) { // Clear the mappings as they're no longer needed values.clear(); return Collections.emptySet(); } // List of Storables we're going to publish List<Storable> toRemove = new ArrayList<Storable>(); List<Storable> toPublish = new ArrayList<Storable>(); synchronized (values) { gnutellaFileView.getReadLock().lock(); try { // Step One: Add every new FileDesc to the Map for(FileDesc fd : gnutellaFileView) { URN urn = fd.getSHA1Urn(); KUID primaryKey = KUIDUtils.toKUID(urn); if (!values.containsKey(primaryKey)) { long fileSize = fd.getFileSize(); HashTree hashTree = tigerTreeCache.get().getHashTree(urn); byte[] ttroot = null; if (hashTree != null) { ttroot = hashTree.getRootHashBytes(); } AltLocValue value = altLocValueFactory.createAltLocValueForSelf(fileSize, ttroot); values.put(primaryKey, new Storable(primaryKey, value)); } } } finally { gnutellaFileView.getReadLock().unlock(); } // Step Two: Remove every Storable that is no longer // associated with a FileDesc (i.e. the FileDesc was deleted) // and create a List of Storable that are rare and // must be republished for (Iterator<Storable> it = values.values().iterator(); it.hasNext(); ) { Storable storable = it.next(); KUID primaryKey = storable.getPrimaryKey(); URN urn = KUIDUtils.toURN(primaryKey); // For each URN check if the FileDesc still exists FileDesc fd = gnutellaFileView.getFileDesc(urn); // If it doesn't then remove it from the values map and // replace the entity value with the empty value // which will effectively remove the key-value mapping // from the DHT. if (fd == null) { storable = new Storable(primaryKey, DHTValue.EMPTY_VALUE); it.remove(); toRemove.add(storable); // And if it does then check if it is rare and needs // publishing. } else if (fd.isRareFile() && DatabaseUtils.isPublishingRequired(storable)) { toPublish.add(storable); } } } // Publish things always in a different order Collections.shuffle(toPublish); // Delete the things we want to remove from the DHT // first and continue with the things we want to publish return new MultiCollection<Storable>(toRemove, toPublish); } /* * (non-Javadoc) * @see org.limewire.mojito.db.StorableModel#handleStoreResult(org.limewire.mojito.db.Storable, org.limewire.mojito.result.StoreResult) */ public void handleStoreResult(Storable storable, StoreResult result) { } /* * (non-Javadoc) * @see org.limewire.mojito.db.StorableModel#handleContactChange() */ public void handleContactChange() { } @Override public String toString() { StringBuilder buffer = new StringBuilder("AltLocPublisher: "); synchronized (values) { buffer.append(CollectionUtils.toString(values.values())); } return buffer.toString(); } }