package org.limewire.core.impl.search.browse;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.limewire.core.api.library.FriendLibrary;
import org.limewire.core.api.library.RemoteLibraryEvent;
import org.limewire.core.api.library.RemoteLibraryManager;
import org.limewire.core.api.library.RemoteLibraryState;
import org.limewire.core.api.search.SearchListener;
import org.limewire.core.api.search.SearchResult;
import org.limewire.core.api.search.browse.BrowseStatus;
import org.limewire.core.api.search.browse.BrowseStatusListener;
import org.limewire.core.api.search.browse.BrowseStatus.BrowseState;
import org.limewire.friend.api.Friend;
import org.limewire.listener.EventListener;
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.event.ListEventListener;
class FriendSingleBrowseSearch extends AbstractBrowseSearch {
private final Friend friend;
private final RemoteLibraryManager remoteLibraryManager;
private final ExecutorService executorService;
private final ListEventListener<FriendLibrary> friendLibraryListEventListener = new FriendLibraryListEventListener();
private final EventListener<RemoteLibraryEvent> eventAdapter = new RemoteLibraryToBrowseEventAdapter();
private final AtomicReference<FriendLibrary> currentLibrary = new AtomicReference<FriendLibrary>();
private boolean hasRegisteredListener = false;
private final AtomicBoolean hasStopped = new AtomicBoolean(false);
private Future startFuture = null;
/**
* @param friend the person to be browsed - can not be anonymous or null
*/
public FriendSingleBrowseSearch(RemoteLibraryManager remoteLibraryManager, Friend friend, ExecutorService executorService) {
assert(friend != null && !friend.isAnonymous());
this.friend = friend;
this.remoteLibraryManager = remoteLibraryManager;
this.executorService = executorService;
}
@Override
public void start() {
startFuture = executorService.submit(new Runnable() {
public void run() {
for (SearchListener listener : searchListeners) {
if(hasStopped.get())
return;
listener.searchStarted(FriendSingleBrowseSearch.this);
}
if(!hasStopped.get()) {
synchronized(FriendSingleBrowseSearch.this) {
installListener();
}
}
if(!hasStopped.get()) {
startFriendBrowse();
}
}
});
}
@Override
public void stop() {
if(startFuture != null && !startFuture.isDone()) {
startFuture.cancel(true);
}
hasStopped.set(true);
for (SearchListener listener : searchListeners) {
listener.searchStopped(FriendSingleBrowseSearch.this);
}
synchronized(this) {
removeListener();
}
}
private void startFriendBrowse() {
FriendLibrary library = remoteLibraryManager.getFriendLibrary(friend);
if (library == null) {
// Failed!
fireBrowseStatusChanged(BrowseState.OFFLINE, friend);
for (SearchListener listener : searchListeners) {
listener.searchStopped(FriendSingleBrowseSearch.this);
}
} else {
setLibrary(library);
if(library.getState() == RemoteLibraryState.LOADING){
library.addListener(eventAdapter);
} else {
loadLibrary();
}
}
}
/**Loads a snapshot of the available files, alerts BrowseStatusListeners that we have loaded,
* and SearchListeners that the search has stopped.*/
private void loadLibrary(){
FriendLibrary friendLibrary = remoteLibraryManager.getFriendLibrary(friend);
List<SearchResult> searchResults = new ArrayList<SearchResult>(friendLibrary.size());
for (SearchResult result : friendLibrary) {
searchResults.add(result);
}
// add all files
for (SearchListener listener : searchListeners) {
listener.handleSearchResults(this, searchResults);
}
fireBrowseStatusChanged(BrowseState.LOADED);
for (SearchListener listener : searchListeners) {
listener.searchStopped(FriendSingleBrowseSearch.this);
}
}
/**Adds friendLibraryListEventListener to the FriendLibraryList*/
private void installListener() {
remoteLibraryManager.getFriendLibraryList().addListEventListener(friendLibraryListEventListener);
hasRegisteredListener = true;
}
/**Removes friendLibraryListEventListener from the FriendLibraryList.
* Removes libraryPropertyChangeLister from the friend library if necessary.*/
private void removeListener() {
if(hasRegisteredListener) {
remoteLibraryManager.getFriendLibraryList().removeListEventListener(friendLibraryListEventListener);
}
hasRegisteredListener = false;
setLibrary(null);
}
private void setLibrary(FriendLibrary newLibrary){
FriendLibrary oldLibrary = currentLibrary.getAndSet(newLibrary);
if(newLibrary == oldLibrary){
return;
}
if(oldLibrary != null){
oldLibrary.removeListener(eventAdapter);
}
if (newLibrary == null) {
return;
}
newLibrary.addListener(eventAdapter);
if(newLibrary.getState() == RemoteLibraryState.LOADED){
fireBrowseStatusChanged(BrowseState.UPDATED);
}
}
private void fireBrowseStatusChanged(BrowseState state, Friend... friends){
BrowseStatus status = new BrowseStatus(FriendSingleBrowseSearch.this, state, friends);
for (BrowseStatusListener listener : browseStatusListeners) {
listener.statusChanged(status);
}
}
private class FriendLibraryListEventListener implements ListEventListener<FriendLibrary> {
@Override
public void listChanged(ListEvent listChanges) {
while (listChanges.next()) {
if (listChanges.getType() == ListEvent.INSERT) {
FriendLibrary newLibrary = (FriendLibrary) listChanges.getSourceList().get(listChanges.getIndex());
if (newLibrary.getFriend().getId().equals(friend.getId())) {//There is a new library for our friend!
setLibrary(remoteLibraryManager.getFriendLibrary(friend));
}
} else if (listChanges.getType() == ListEvent.DELETE && remoteLibraryManager.getFriendLibrary(friend) == null){
//our friend has logged off
setLibrary(null);
fireBrowseStatusChanged(BrowseState.OFFLINE, friend);
}
}
}
}
private class RemoteLibraryToBrowseEventAdapter implements EventListener<RemoteLibraryEvent> {
@Override
public void handleEvent(RemoteLibraryEvent event) {
switch (event.getType()) {
case STATE_CHANGED:
RemoteLibraryState state = event.getState();
if (state != RemoteLibraryState.LOADING) {
// The list has changed - tell the listeners
if (state == RemoteLibraryState.LOADED) {
fireBrowseStatusChanged(BrowseState.UPDATED);
} else {
fireBrowseStatusChanged(BrowseState.FAILED, friend);
}
}
break;
case RESULTS_ADDED:
case RESULTS_CLEARED:
case RESULTS_REMOVED:
fireBrowseStatusChanged(BrowseState.UPDATED);
break;
}
}
}
@Override
public void repeat() {
stop();
hasStopped.set(false);
start();
}
}