package org.limewire.core.impl.friend; import java.util.Date; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicBoolean; import org.limewire.collection.Periodic; import org.limewire.core.api.browse.server.BrowseTracker; import org.limewire.friend.api.Friend; import org.limewire.friend.api.FriendEvent; import org.limewire.friend.api.FriendException; import org.limewire.friend.api.FriendPresence; import org.limewire.friend.api.feature.Feature; import org.limewire.friend.api.feature.FeatureTransport; import org.limewire.friend.api.feature.LibraryChangedNotifier; import org.limewire.friend.api.feature.LibraryChangedNotifierFeature; import org.limewire.inject.EagerSingleton; import org.limewire.listener.BlockingEvent; import org.limewire.listener.EventListener; import org.limewire.listener.ListenerSupport; import org.limewire.logging.Log; import org.limewire.logging.LogFactory; import org.limewire.util.Clock; import com.google.inject.Inject; import com.google.inject.name.Named; import com.limegroup.gnutella.library.FileViewChangeEvent; import com.limegroup.gnutella.library.FileViewManager; import com.limegroup.gnutella.library.Library; import com.limegroup.gnutella.library.LibraryStatusEvent; /** * Sends library changed messages to friends when:<BR> * 1) File manager is finished loading<BR> * OR<BR> * 2) A friend's sharelist changes */ @EagerSingleton class FriendShareListRefresher { private static final Log LOG = LogFactory.getLog(FriendShareListRefresher.class); private final Clock clock; private final BrowseTracker tracker; private final ScheduledExecutorService scheduledExecutorService; private final Map<String, Friend> friendMap; /** A map of change senders per each id. */ private final ConcurrentMap<String, LibraryChangedSender> changeSenders; // Package private for testing final AtomicBoolean fileManagerLoaded = new AtomicBoolean(false); @Inject FriendShareListRefresher(Clock clock, BrowseTracker tracker, @Named("backgroundExecutor")ScheduledExecutorService scheduledExecutorService, @Named("available") Map<String, Friend> friendMap) { this.clock = clock; this.tracker = tracker; this.scheduledExecutorService = scheduledExecutorService; this.changeSenders = new ConcurrentHashMap<String, LibraryChangedSender>(); this.friendMap = friendMap; } @Inject void register(Library library) { library.addManagedListStatusListener(new FinishedLoadingListener()); } @Inject void register(final FileViewManager fileViewManager, @Named("available") ListenerSupport<FriendEvent> friendSupport) { fileViewManager.addListener(new EventListener<FileViewChangeEvent>() { public void handleEvent(FileViewChangeEvent event) { switch(event.getType()) { case FILE_ADDED: case FILE_REMOVED: case FILE_META_CHANGED: case FILE_CHANGED: case FILES_CLEARED: triggerChangeSender(event.getSource().getName()); break; } } }); friendSupport.addListener(new EventListener<FriendEvent>() { @Override public void handleEvent(FriendEvent event) { LOG.debugf("Received friend event {0}", event); switch(event.getType()) { case DELETE: case REMOVED: removeChangeSender(event.getData().getId()); break; case ADDED: fileViewManager.getFileViewForId(event.getData().getId()); break; } } }); } private void triggerChangeSender(String id) { LOG.debugf("Change triggered for id {0}", id); if(fileManagerLoaded.get()) { Friend friend = friendMap.get(id); if(friend != null) { LOG.debugf("Triggering library change for friend {0}", friend); LibraryChangedSender sender = changeSenders.get(id); if(sender == null) { LOG.debugf("No existing sender for friend {0}, creating a new one", friend); LibraryChangedSender newSender = new LibraryChangedSender(friend); sender = changeSenders.putIfAbsent(id, newSender); if(sender == null) { sender = newSender; } } sender.scheduleSendRefreshCheck(); } else { LOG.debugf("Not triggering library change for id {0} because no friend was available", id); } } } private void removeChangeSender(String id) { LibraryChangedSender sender = changeSenders.remove(id); if(sender != null) { LOG.debugf("Removing change trigger for id {0}", id); sender.cancel(); } } /** * Sends refresh notifications to the presences that have the {@link LibraryChangedNotifierFeature}. */ void sendRefreshNotificationsToPresences(Iterable<FriendPresence> presences) { for(FriendPresence presence : presences) { @SuppressWarnings("unchecked") Feature<LibraryChangedNotifier> feature = presence.getFeature(LibraryChangedNotifierFeature.ID); if (feature != null) { FeatureTransport<LibraryChangedNotifier> transport = presence.getTransport(LibraryChangedNotifierFeature.class); try { transport.sendFeature(presence, feature.getFeature()); } catch (FriendException e) { LOG.error("library changed notification failed", e); } } else { LOG.debugf("no library refresh for presence: {0}", presence); } } } private class FinishedLoadingListener implements EventListener<LibraryStatusEvent> { @BlockingEvent public void handleEvent(LibraryStatusEvent evt) { switch(evt.getType()) { case LOAD_COMPLETE: fileManagerLoaded.set(true); for(Friend friend : friendMap.values()) { tracker.sentRefresh(friend.getId()); sendRefreshNotificationsToPresences(friend.getPresences().values()); } break; } } } private class LibraryChangedSender { private final Friend friend; private final Periodic libraryRefreshPeriodic; LibraryChangedSender(Friend friend){ this.friend = friend; this.libraryRefreshPeriodic = new Periodic(new ScheduledLibraryRefreshSender(), scheduledExecutorService, clock); } /** * Schedules an immediate check if a library refresh should be sent * to the friend. */ void scheduleSendRefreshCheck() { libraryRefreshPeriodic.rescheduleIfLater(5000); } void cancel() { libraryRefreshPeriodic.unschedule(); } private class ScheduledLibraryRefreshSender implements Runnable { @Override public void run() { BrowseTracker browseTracker = FriendShareListRefresher.this.tracker; Date lastBrowseTime = browseTracker.lastBrowseTime(friend.getId()); Date lastRefreshTime = browseTracker.lastRefreshTime(friend.getId()); LOG.debugf("Running library periodic for friend {0}, lastBrowseTime {1}, lastRefreshTime {2}", friend, lastBrowseTime, lastRefreshTime); if(lastBrowseTime != null && (lastRefreshTime == null || lastBrowseTime.after(lastRefreshTime))) { browseTracker.sentRefresh(friend.getId()); sendRefreshNotificationsToPresences(friend.getPresences().values()); } } } } }