package com.limegroup.gnutella.library; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.limewire.concurrent.FutureEvent; import org.limewire.concurrent.ListeningFuture; import org.limewire.core.api.Category; import org.limewire.core.api.file.CategoryManager; import org.limewire.core.settings.LibrarySettings; import org.limewire.core.settings.URNSettings; import org.limewire.listener.EventBroadcaster; import org.limewire.listener.EventListener; import org.limewire.listener.SourcedEventMulticaster; import org.limewire.util.StringUtils; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.assistedinject.Assisted; import com.limegroup.gnutella.URN; import com.limegroup.gnutella.hashing.AudioHashingUtils; import com.limegroup.gnutella.library.SharedFileCollectionChangeEvent.Type; import com.limegroup.gnutella.tigertree.HashTreeCache; /** * A collection of FileDescs containing files that may be shared with one or more people. */ class SharedFileCollectionImpl extends AbstractFileCollection implements SharedFileCollection { private final int collectionId; private final Provider<LibraryFileData> data; private final HashTreeCache treeCache; private final EventBroadcaster<SharedFileCollectionChangeEvent> sharedBroadcaster; private final List<String> defaultFriendIds; private final boolean publicCollection; private final CategoryManager categoryManager; private final UrnCache urnCache; @Inject public SharedFileCollectionImpl(Provider<LibraryFileData> data, LibraryImpl managedList, SourcedEventMulticaster<FileViewChangeEvent, FileView> multicaster, EventBroadcaster<SharedFileCollectionChangeEvent> sharedCollectionBroadcaster, CategoryManager categoryManager, UrnCache urnCache, @Assisted int id, HashTreeCache treeCache, @Assisted boolean publicCollection, @Assisted String... defaultFriendIds) { super(managedList, multicaster); this.collectionId = id; this.data = data; this.treeCache = treeCache; this.sharedBroadcaster = sharedCollectionBroadcaster; this.publicCollection = publicCollection; this.categoryManager = categoryManager; this.urnCache = urnCache; if(defaultFriendIds.length == 0) { this.defaultFriendIds = Collections.emptyList(); } else { this.defaultFriendIds = Collections.unmodifiableList(Arrays.asList(defaultFriendIds)); } } @Override public int getId() { return collectionId; } public String getName() { return data.get().getNameForCollection(collectionId); } public void setName(String name) { if(data.get().setNameForCollection(collectionId, name)) { sharedBroadcaster.broadcast(new SharedFileCollectionChangeEvent(Type.NAME_CHANGED, this, name)); } } @Override public void addFriend(String id) { if(data.get().addFriendToCollection(collectionId, id)) { sharedBroadcaster.broadcast(new SharedFileCollectionChangeEvent(Type.FRIEND_ADDED, this, id)); } } @Override public boolean removeFriend(String id) { if(data.get().removeFriendFromCollection(collectionId, id)) { sharedBroadcaster.broadcast(new SharedFileCollectionChangeEvent(Type.FRIEND_REMOVED, this, id)); return true; } else { return false; } } @Override public List<String> getFriendList() { List<String> cached = data.get().getFriendsForCollection(collectionId); if(defaultFriendIds.isEmpty()) { return cached; } else if(cached.isEmpty()) { return defaultFriendIds; } else { List<String> friends = new ArrayList<String>(cached.size() + defaultFriendIds.size()); friends.addAll(defaultFriendIds); friends.addAll(cached); return friends; } } @Override public void setFriendList(List<String> ids) { List<String> oldIds = data.get().setFriendsForCollection(collectionId, ids); if(oldIds != null) { // if it changed, broadcast the change. sharedBroadcaster.broadcast(new SharedFileCollectionChangeEvent(Type.FRIEND_IDS_CHANGED, this, oldIds, ids)); } } @Override public String toString() { return StringUtils.toString(this) + ", name: " + getName(); } @Override protected boolean addFileDescImpl(FileDesc fileDesc) { if(super.addFileDescImpl(fileDesc)) { // if no root, calculate one and propagate it. if(fileDesc.getTTROOTUrn() == null) { // Schedule all additions for having a hash tree root. URN root = treeCache.getOrScheduleHashTreeRoot(fileDesc); if(root != null) { for(FileDesc fd : library.getFileDescsMatching(fileDesc.getSHA1Urn())) { fd.addUrn(root); } } } // if this file already has a SHA1, try creating the nms1. // we want to ensure the SHA1 and FD are valid for this list // so if they don't exist, we will wait till after the SHA1 // has been calculated. the nms1 and sha1 use the same thread // and calculating the SHA1 is more important. // if no SHA1 exists yet, we're guarenteed to recieve // a FILE_META_CHANGED event if(fileDesc.getSHA1Urn() != null) { calculateNonMetaDataHash(fileDesc); } return true; } else { return false; } } @Override protected void fileMetaChanged(final FileDesc fileDesc) { super.fileMetaChanged(fileDesc); // if this FileDesc still exists in this list, try creating NMS1. // this will only be called on a new library load or if a file // was added directly to the shared list. if(contains(fileDesc)) { calculateNonMetaDataHash(fileDesc); } } /** * Attempts to calculate the NMS1 for this FileDesc if we're * allowing NMS1 and the FileDesc doesn't already contain it * and it can be created for this file type. */ private void calculateNonMetaDataHash(final FileDesc fileDesc) { if(URNSettings.USE_NON_METADATA_HASH.get() && fileDesc.getNMS1Urn() == null && AudioHashingUtils.canCreateNonMetaDataSHA1(fileDesc.getFile())) { ListeningFuture<URN> urnFuture = urnCache.calculateAndCacheNMS1(fileDesc.getFile()); urnFuture.addFutureListener(new EventListener<FutureEvent<URN>>(){ @Override public void handleEvent(FutureEvent<URN> event) { URN urn = event.getResult(); if(urn != null && urn.isNMS1()) { for(FileDesc fd : library.getFileDescsMatching(fileDesc.getSHA1Urn())) { fd.addUrn(urn); } } } }); } } @Override protected void initialize() { super.initialize(); addPendingManagedFiles(); } /** * This method initializes the friend file list. It adds the files * that are shared with the friend represented by this list. This * is necessary because friend file lists are populated/unpopulated when * needed, not upon startup. */ protected void addPendingManagedFiles() { // add files from the MASTER list which are for the current friend // normally we would not want to lock the master list while adding // items internally... but it's OK here because we're guaranteed // that nothing is listening to this list, since this will happen // immediately after construction. library.getReadLock().lock(); try { for (FileDesc fd : library) { if(isPending(fd.getFile(), fd)) { add(fd); } } } finally { library.getReadLock().unlock(); } } /** * Returns false if it's an {@link IncompleteFileDesc}. */ @Override protected boolean isFileDescAllowed(FileDesc fileDesc) { if (fileDesc instanceof IncompleteFileDesc) { return false; } else { return isFileAllowed(fileDesc.getFile()); } } @Override protected boolean isPending(File file, FileDesc fd) { return data.get().isFileInCollection(file, collectionId); } @Override protected void saveChange(File file, boolean added) { data.get().setFileInCollection(file, collectionId, added); } @Override protected boolean clearImpl() { data.get().setFilesInCollection(this, collectionId, false); return super.clearImpl(); } @Override void dispose() { super.dispose(); data.get().removeCollection(collectionId); } @Override protected void fireAddEvent(FileDesc fileDesc) { super.fireAddEvent(fileDesc); } @Override protected void fireRemoveEvent(FileDesc fileDesc) { super.fireRemoveEvent(fileDesc); } @Override protected void fireChangeEvent(FileDesc oldFileDesc, FileDesc newFileDesc) { super.fireChangeEvent(oldFileDesc, newFileDesc); } @Override public boolean isFileAllowed(File file) { if(!library.isFileAllowed(file)) { return false; } if(isPublic()) { Category category = categoryManager.getCategoryForFile(file); if(category == Category.DOCUMENT && !LibrarySettings.ALLOW_DOCUMENT_GNUTELLA_SHARING.getValue()) { return false; } } return true; } @Override public boolean isDirectoryAllowed(File folder) { return library.isDirectoryAllowed(folder); } @Override public boolean isPublic() { return publicCollection; } }