package com.limegroup.gnutella.library; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.ObjectInputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.EnumMap; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.limewire.collection.CollectionUtils; import org.limewire.core.api.Category; import org.limewire.core.settings.LibrarySettings; import org.limewire.core.settings.SharingSettings; import org.limewire.logging.Log; import org.limewire.logging.LogFactory; import org.limewire.setting.AbstractSettingsGroup; import org.limewire.setting.SettingsGroupManager; import org.limewire.util.CommonUtils; import org.limewire.util.FileUtils; import org.limewire.util.GenericsUtils; import org.limewire.util.GenericsUtils.ScanMode; import com.limegroup.gnutella.CategoryConverter; class LibraryFileData extends AbstractSettingsGroup { private static final Log LOG = LogFactory.getLog(LibraryFileData.class); private final ReadWriteLock lock = new ReentrantReadWriteLock(); /** Default file extensions. */ private final String DEFAULT_MANAGED_EXTENSIONS_STRING = "xml;txt;ps;rtf;tex;mp3;mp4;wav;wax;au;aif;aiff;"+ "ra;ram;mp2v;mlv;mpa;mpv2;mid;midi;rmi;aifc;snd;flac;fla;flv;"+ "mpg;mpeg;qt;mov;avi;mpe;swf;dcr;gif;jpg;jpeg;jpe;png;tif;tiff;bmp;"+ "zip;gz;gzip;hqx;tar;tgz;z;rmj;lqt;rar;ace;sit;smi;img;ogg;rm;"+ "bin;dmg;jve;nsv;med;mod;7z;iso;lwtp;pmf;m4a;bz2;sea;pf;arc;arj;"+ "bz;tbz;mime;taz;ua;toast;lit;rpm;deb;pkg;sxw;l6t;srt;sub;idx;mkv;"+ "ogm;shn;dvi;rmvp;kar;cdg;ccd;cue;c;h;m;java;jar;pl;py;pyc;"+ "pyo;pyz;latex"; private final Collection<String> DEFAULT_MANAGED_EXTENSIONS = Collections.unmodifiableList(Arrays.asList(DEFAULT_MANAGED_EXTENSIONS_STRING.split(";"))); private static enum Version { // for prior versions [before 5.0], see OldLibraryData & LibraryConverter ONE, // the first ever version [active 5.0 -> 5.1] TWO, // [active 5.2] THREE; // the current version [active 5.3] } private static final String CURRENT_VERSION_KEY = "CURRENT_VERSION"; private static final String USER_EXTENSIONS_KEY = "USER_EXTENSIONS"; private static final String USER_REMOVED_KEY = "USER_REMOVED"; // private static final String MANAGED_DIRECTORIES_KEY = "MANAGED_DIRECTORIES"; // private static final String DO_NOT_MANAGE_KEY = "DO_NOT_MANAGE"; // private static final String EXCLUDE_FILES_KEY = "EXCLUDE_FILES"; private static final String SHARE_DATA_KEY = "SHARE_DATA"; private static final String FILE_DATA_KEY = "FILE_DATA"; private static final String COLLECTION_NAME_KEY = "COLLECTION_NAMES"; private static final String COLLECTION_SHARE_DATA_KEY = "COLLECTION_SHARE_DATA"; private static final String SAFE_URNS = "SAFE_URNS"; static final Integer DEFAULT_SHARED_COLLECTION_ID = 0; private static final Integer MIN_COLLECTION_ID = 1; private final Version CURRENT_VERSION = Version.THREE; private final Set<String> userExtensions = new HashSet<String>(); private final Set<String> userRemoved = new HashSet<String>(); private final Map<String, List<Integer>> fileData = new HashMap<String, List<Integer>>(); private final SortedMap<Integer, String> collectionNames = new TreeMap<Integer, String>(); private final Map<Integer, List<String>> collectionShareData = new HashMap<Integer, List<String>>(); private final Set<String> safeUrns = new HashSet<String>(); private volatile boolean dirty = false; private final File saveFile = new File(CommonUtils.getUserSettingsDir(), "library5.dat"); private final File backupFile = new File(CommonUtils.getUserSettingsDir(), "library5.bak"); private volatile boolean loaded = false; private volatile Set<String> managedExtensions = Collections.unmodifiableSet(new HashSet<String>()); private volatile Set<String> extensionsInManagedCategories = Collections.unmodifiableSet(new HashSet<String>()); LibraryFileData() { SettingsGroupManager.instance().addSettingsGroup(this); updateManagedExtensions(); } public boolean isLoaded() { return loaded; } @Override public void reload() { load(); } @Override public boolean revertToDefault() { clear(); return true; } private void clear() { lock.writeLock().lock(); try { dirty = true; userExtensions.clear(); userRemoved.clear(); fileData.clear(); updateManagedExtensions(); } finally { lock.writeLock().unlock(); } } public boolean save() { if(!loaded || !dirty) { return false; } Map<String, Object> save = new HashMap<String, Object>(); lock.readLock().lock(); try { save.put(CURRENT_VERSION_KEY, CURRENT_VERSION); save.put(USER_EXTENSIONS_KEY, userExtensions); save.put(USER_REMOVED_KEY, userRemoved); save.put(FILE_DATA_KEY, fileData); save.put(COLLECTION_NAME_KEY, collectionNames); save.put(COLLECTION_SHARE_DATA_KEY, collectionShareData); save.put(SAFE_URNS, safeUrns); if(FileUtils.writeWithBackupFile(save, backupFile, saveFile, LOG)) { dirty = false; } } finally { lock.readLock().unlock(); } return true; } void load() { boolean failed = false; if(!loadFromFile(saveFile)) { failed = !loadFromFile(backupFile); } dirty = failed; loaded = true; } private boolean loadFromFile(File file) { Map<String, Object> readMap = null; try { ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file))); Object read = in.readObject(); readMap = GenericsUtils.scanForMap(read, String.class, Object.class, ScanMode.REMOVE); if (readMap != null) { Object currentVersion = readMap.get("CURRENT_VERSION"); if(currentVersion == null) { currentVersion = Version.ONE; } if(currentVersion instanceof Version) { initializeFromVersion(((Version)currentVersion), readMap); } else { return false; } return true; } } catch(Throwable throwable) { LOG.error("Error loading library", throwable); } return false; } /** * Initializes the read map assuming it's a particular version. */ private void initializeFromVersion(Version version, Map<String, Object> readMap) { Set<String> userExtensions; Set<String> userRemoved; Map<String, List<Integer>> fileData; Map<Integer, String> collectionNames; Map<Integer, List<String>> collectionShareData; Set<String> safeUrns; switch(version) { case ONE: userExtensions = GenericsUtils.scanForSet(readMap.get(USER_EXTENSIONS_KEY), String.class, ScanMode.REMOVE); userRemoved = GenericsUtils.scanForSet(readMap.get(USER_REMOVED_KEY), String.class, ScanMode.REMOVE); Map<File, FileProperties> oldShareData = GenericsUtils.scanForMap(readMap.get(SHARE_DATA_KEY), File.class, FileProperties.class, ScanMode.REMOVE); fileData = new HashMap<String, List<Integer>>(); collectionNames = new HashMap<Integer, String>(); collectionShareData = new HashMap<Integer, List<String>>(); convertShareData(oldShareData, fileData, collectionNames, collectionShareData); final Map<String, List<Integer>> fileDataFinal = fileData; LibraryConverterHelper helper = new LibraryConverterHelper(new LibraryConverterHelper.FileAdder() { @Override public void addFile(File file) { if(!fileDataFinal.containsKey(createKey(file))) { fileDataFinal.put(createKey(file), Collections.<Integer>emptyList()); } } }); //add save directories to library Set<File> convertedDirectories = new HashSet<File>(); List<File> emptyList = Collections.emptyList(); helper.convertSaveDirectories(emptyList, emptyList, convertedDirectories); safeUrns = new HashSet<String>(); break; case TWO: fileData = new HashMap<String, List<Integer>>(); Map<File, List<Integer>> oldFileData = GenericsUtils.scanForMapOfList(readMap.get(FILE_DATA_KEY), File.class, List.class, Integer.class, ScanMode.REMOVE); convertShareData(oldFileData, fileData); userExtensions = GenericsUtils.scanForSet(readMap.get(USER_EXTENSIONS_KEY), String.class, ScanMode.REMOVE); userRemoved = GenericsUtils.scanForSet(readMap.get(USER_REMOVED_KEY), String.class, ScanMode.REMOVE); collectionNames = GenericsUtils.scanForMap(readMap.get(COLLECTION_NAME_KEY), Integer.class, String.class, ScanMode.REMOVE); collectionShareData = GenericsUtils.scanForMapOfList(readMap.get(COLLECTION_SHARE_DATA_KEY), Integer.class, List.class, String.class, ScanMode.REMOVE); safeUrns = GenericsUtils.scanForSet(readMap.get(SAFE_URNS), String.class, ScanMode.REMOVE); break; case THREE: fileData = GenericsUtils.scanForMapOfList(readMap.get(FILE_DATA_KEY), String.class, List.class, Integer.class, ScanMode.REMOVE); userExtensions = GenericsUtils.scanForSet(readMap.get(USER_EXTENSIONS_KEY), String.class, ScanMode.REMOVE); userRemoved = GenericsUtils.scanForSet(readMap.get(USER_REMOVED_KEY), String.class, ScanMode.REMOVE); collectionNames = GenericsUtils.scanForMap(readMap.get(COLLECTION_NAME_KEY), Integer.class, String.class, ScanMode.REMOVE); collectionShareData = GenericsUtils.scanForMapOfList(readMap.get(COLLECTION_SHARE_DATA_KEY), Integer.class, List.class, String.class, ScanMode.REMOVE); safeUrns = GenericsUtils.scanForSet(readMap.get(SAFE_URNS), String.class, ScanMode.REMOVE); break; default: throw new IllegalStateException("Invalid version: " + version); } fileData = internKeys(fileData); safeUrns = internSafeUrns(safeUrns); validateCollectionData(fileData, collectionNames, collectionShareData); lock.writeLock().lock(); try { clear(); this.userExtensions.addAll(lowercase(userExtensions)); this.userRemoved.addAll(userRemoved); this.fileData.putAll(fileData); this.collectionNames.putAll(collectionNames); this.collectionShareData.putAll(collectionShareData); this.safeUrns.addAll(safeUrns); updateManagedExtensions(); } finally { lock.writeLock().unlock(); } } private static Map<String,List<Integer>> internKeys(Map<String, List<Integer>> oldFileData) { Map<String,List<Integer>> newFileData = new HashMap<String, List<Integer>>(); for ( Map.Entry<String, List<Integer>> entry : oldFileData.entrySet() ) { newFileData.put(entry.getKey().intern(), entry.getValue()); } return newFileData; } private static Set<String> internSafeUrns(Set<String> oldSafeUrns) { Set<String> newSafeUrns = new HashSet<String>(); for ( String entry : oldSafeUrns ) { newSafeUrns.add(entry.intern()); } return newSafeUrns; } private void validateCollectionData(Map<String, List<Integer>> fileData, Map<Integer, String> collectionNames, Map<Integer, List<String>> collectionShareData) { // TODO: Do some validation } /** Converts 5.0 & 5.1 style share data into 5.2-style collections. */ private void convertShareData(Map<File, FileProperties> oldShareData, Map<String, List<Integer>> fileData, Map<Integer, String> collectionNames, Map<Integer, List<String>> collectionShareData) { int currentId = MIN_COLLECTION_ID; Map<String, Integer> friendToCollectionMap = new HashMap<String, Integer>(); for(Map.Entry<File, FileProperties> data : oldShareData.entrySet()) { File file = data.getKey(); FileProperties shareData = data.getValue(); if (shareData == null || ((shareData.friends == null || shareData.friends.isEmpty()) && !shareData.gnutella)) { fileData.put(createKey(file), Collections.<Integer> emptyList()); } else { if (shareData.friends != null) { for (String friend : shareData.friends) { Integer collectionId = friendToCollectionMap.get(friend); if (collectionId == null) { collectionId = currentId; friendToCollectionMap.put(friend, collectionId); collectionNames.put(collectionId, friend); List<String> shareList = new ArrayList<String>(1); shareList.add(friend); collectionShareData.put(collectionId, shareList); currentId++; } List<Integer> collections = fileData.get(createKey(file)); if (collections == null || collections == Collections.<Integer> emptyList()) { collections = new ArrayList<Integer>(1); fileData.put(createKey(file), collections); } collections.add(collectionId); } } if (shareData.gnutella) { List<Integer> collections = fileData.get(createKey(file)); if (collections == null || collections == Collections.<Integer> emptyList()) { collections = new ArrayList<Integer>(1); fileData.put(createKey(file), collections); } collections.add(DEFAULT_SHARED_COLLECTION_ID); } } } } /** Converts 5.0 & 5.1 style share data into 5.2-style collections. */ private void convertShareData(Map<File, List<Integer>> oldFileData, Map<String, List<Integer>> fileData) { for(Map.Entry<File, List<Integer>> data : oldFileData.entrySet()) { fileData.put(createKey(data.getKey()), data.getValue()); } } private static String createKey(File file) { return file.getPath().intern(); } /** Returns true if this URN was marked as safe. */ boolean isFileSafe(String urn) { lock.readLock().lock(); try { return safeUrns.contains(urn); } finally { lock.readLock().unlock(); } } /** Caches the URN as being safe or not. */ void setFileSafe(String urn, boolean safe) { lock.writeLock().lock(); try { if(!safe) { if(safeUrns.remove(urn)) { dirty = true; } } else { if(safeUrns.add(urn)) { dirty = true; } } } finally { lock.writeLock().unlock(); } } /** Clears all file data. */ void clearFileData() { lock.writeLock().lock(); try { if(!fileData.isEmpty()) { fileData.clear(); dirty = true; } } finally { lock.writeLock().unlock(); } } /** * Adds a managed file. */ void addManagedFile(File file) { lock.writeLock().lock(); try { boolean changed = false; String key = createKey(file); if(!fileData.containsKey(key)) { fileData.put(key, Collections.<Integer>emptyList()); changed = true; } dirty |= changed; } finally { lock.writeLock().unlock(); } } /** * Adds a managed file. */ void addOrRenameManagedFile(File file, File originalFile) { if (originalFile == null) { addManagedFile(file); } lock.writeLock().lock(); try { boolean changed = false; String key = createKey(file); if(!fileData.containsKey(key)) { String originalKey = createKey(originalFile); if (fileData.containsKey(originalKey)) { fileData.put(key, fileData.get(originalKey)); fileData.remove(originalKey); } else { fileData.put(key, Collections.<Integer>emptyList()); } changed = true; } dirty |= changed; } finally { lock.writeLock().unlock(); } } /** * Removes a file from being managed. */ void removeManagedFile(File file) { lock.writeLock().lock(); try { boolean changed = fileData.remove(createKey(file)) != null; dirty |= changed; } finally { lock.writeLock().unlock(); } } /** Returns a list of all files that should be managed. */ Iterable<File> getManagedFiles() { List<File> indivFiles = new ArrayList<File>(); lock.readLock().lock(); try { for ( String key : fileData.keySet() ) { indivFiles.add(new File(key)); } } finally { lock.readLock().unlock(); } return indivFiles; } /** Retuns true if the given folder is the incomplete folder. */ boolean isIncompleteDirectory(File folder) { return FileUtils.canonicalize(SharingSettings.INCOMPLETE_DIRECTORY.get()).equals(folder); } /** Returns all categories that should be managed. */ public Collection<Category> getManagedCategories() { Set<Category> categories = EnumSet.noneOf(Category.class); if(LibrarySettings.MANAGE_AUDIO.getValue()) { categories.add(Category.AUDIO); } if(LibrarySettings.MANAGE_DOCUMENTS.getValue()) { categories.add(Category.DOCUMENT); } if(LibrarySettings.MANAGE_IMAGES.getValue()) { categories.add(Category.IMAGE); } if(LibrarySettings.MANAGE_OTHER.getValue()) { categories.add(Category.OTHER); } if(LibrarySettings.MANAGE_PROGRAMS.getValue() && LibrarySettings.ALLOW_PROGRAMS.getValue()) { categories.add(Category.PROGRAM); } if(LibrarySettings.MANAGE_VIDEO.getValue()) { categories.add(Category.VIDEO); } return categories; } /** Sets the new group of categories to manage. */ public void setManagedCategories(Collection<Category> categoriesToManage) { lock.writeLock().lock(); try { LibrarySettings.MANAGE_AUDIO.setValue(categoriesToManage.contains(Category.AUDIO)); LibrarySettings.MANAGE_VIDEO.setValue(categoriesToManage.contains(Category.VIDEO)); LibrarySettings.MANAGE_DOCUMENTS.setValue(categoriesToManage.contains(Category.DOCUMENT)); LibrarySettings.MANAGE_IMAGES.setValue(categoriesToManage.contains(Category.IMAGE)); LibrarySettings.MANAGE_PROGRAMS.setValue(categoriesToManage.contains(Category.PROGRAM)); LibrarySettings.MANAGE_OTHER.setValue(categoriesToManage.contains(Category.OTHER)); updateManagedExtensions(); } finally { lock.writeLock().unlock(); } } /** Returns all extensions that are managed within the managed categories. The returned set cannot be mpodified.*/ public Set<String> getExtensionsInManagedCategories() { return extensionsInManagedCategories; } /** * Returns a Map of Category->Collection<String> that defines * what extensions are in what category. */ Map<Category, Collection<String>> getExtensionsPerCategory() { Set<String> extensions = getManagedExtensions(); Map<Category, Collection<String>> extByCategory = new EnumMap<Category, Collection<String>>(Category.class); for(Category category : Category.values()) { extByCategory.put(category, new ArrayList<String>()); } for(String ext : extensions) { extByCategory.get(CategoryConverter.categoryForExtension(ext)).add(ext); } return extByCategory; } /** * Should be called whenever a method is updating the extensions or the categories that the Library manages. * It rebuilds the managedExtentiosn and extensions in Managed categories set. So that they are always up to * date and can be returned immediately. */ private void updateManagedExtensions() { Set<String> managedExtensions = new HashSet<String>(); try { lock.writeLock().lock(); managedExtensions.addAll(DEFAULT_MANAGED_EXTENSIONS); managedExtensions.addAll(userExtensions); managedExtensions.removeAll(userRemoved); this.managedExtensions = Collections.unmodifiableSet(managedExtensions); Map<Category, Collection<String>> map = getExtensionsPerCategory(); map.keySet().retainAll(getManagedCategories()); extensionsInManagedCategories = Collections.unmodifiableSet(new HashSet<String>(CollectionUtils.flatten(map.values()))); } finally { lock.writeLock().unlock(); } } /** * Returns a new Set with all the currently managed extensions contained within. The returned set cannot be modified. */ Set<String> getManagedExtensions() { return managedExtensions; } /** Sets all extensions that should be managed. */ void setManagedExtensions(Collection<String> newExtensions) { lock.writeLock().lock(); try { newExtensions = lowercase(newExtensions); boolean changed = false; Set<String> removed = new HashSet<String>(); removed.addAll(DEFAULT_MANAGED_EXTENSIONS); removed.removeAll(newExtensions); if(!userRemoved.equals(removed)) { changed = true; userRemoved.clear(); userRemoved.addAll(removed); } Set<String> added = new HashSet<String>(); added.addAll(newExtensions); added.removeAll(DEFAULT_MANAGED_EXTENSIONS); if(!userExtensions.equals(added)) { changed = true; userExtensions.clear(); userExtensions.addAll(added); } if(changed) { updateManagedExtensions(); } dirty |= changed; } finally { lock.writeLock().unlock(); } } public Collection<String> getDefaultManagedExtensions() { return DEFAULT_MANAGED_EXTENSIONS; } /** Returns the IDs of all collections. */ Collection<Integer> getStoredCollectionIds() { lock.readLock().lock(); try { return new ArrayList<Integer>(collectionNames.keySet()); } finally { lock.readLock().unlock(); } } /** Marks the given file as either in the collection or not in the collection. */ void setFileInCollection(File file, int collectionId, boolean contained) { lock.writeLock().lock(); try { if(contained) { dirty |= addFileToCollection(file, collectionId); } else { dirty |= removeFileFromCollection(file, collectionId); } } finally { lock.writeLock().unlock(); } } /** Sets whether or not all the given files should be in the collection. */ void setFilesInCollection(Iterable<FileDesc> fileDescs, int collectionId, boolean contained) { lock.writeLock().lock(); try { if(contained) { for(FileDesc fd : fileDescs) { dirty |= addFileToCollection(fd.getFile(), collectionId); } } else { for(FileDesc fd : fileDescs) { dirty |= removeFileFromCollection(fd.getFile(), collectionId); } } } finally { lock.writeLock().unlock(); } } /** Returns true if the file was removed from the collection, false if it wasn't in the collection. */ private boolean removeFileFromCollection(File file, int collectionId) { List<Integer> collections = fileData.get(createKey(file)); if(collections == null || collections.isEmpty()) { return false; } // cast to ensure we use remove(Object) and not remove(int) return collections.remove((Integer)collectionId); } /** Returns true if file was added to the collection, false if it already was in the collection. */ private boolean addFileToCollection(File file, int collectionId) { boolean changed = false; List<Integer> collections = fileData.get(createKey(file)); if(collections == null || collections == Collections.<Integer>emptyList()) { collections = new ArrayList<Integer>(1); fileData.put(createKey(file), collections); } if(!collections.contains(collectionId)) { collections.add(collectionId); changed = true; } return changed; } /** Returns true if the file is in the given collection. */ boolean isFileInCollection(File file, int collectionId) { lock.readLock().lock(); try { List<Integer> collections = fileData.get(createKey(file)); if(collections != null) { return collections.contains(collectionId); } else { return false; } } finally { lock.readLock().unlock(); } } /** Returns the name of the given collection's id. */ String getNameForCollection(int collectionId) { lock.readLock().lock(); try { return collectionNames.get(collectionId); } finally { lock.readLock().unlock(); } } /** Sets a new name for the collection of the given id. */ boolean setNameForCollection(int collectionId, String name) { lock.writeLock().lock(); try { String oldName = collectionNames.put(collectionId, name); boolean changed = oldName == null || !oldName.equals(name); dirty |= changed; return changed; } finally { lock.writeLock().unlock(); } } /** Returns an ID that will be used for a new collection with the given name. */ int createNewCollection(String name) { lock.writeLock().lock(); try { int nextId = MIN_COLLECTION_ID; if(!collectionNames.isEmpty()) { nextId = collectionNames.lastKey() + 1; } collectionNames.put(nextId, name); dirty = true; return nextId; } finally { lock.writeLock().unlock(); } } /** Removes the collection's share data & name. This assumes all files have already been dereferenced. */ void removeCollection(int collectionId) { lock.writeLock().lock(); try { dirty |= collectionNames.remove(collectionId) != null; dirty |= collectionShareData.remove(collectionId) != null; } finally { lock.writeLock().unlock(); } } /** Adds a new shareId to the given collection's Id. */ boolean addFriendToCollection(int collectionId, String friendId) { lock.writeLock().lock(); try { List<String> ids = collectionShareData.get(collectionId); if(ids == null) { ids = Collections.emptyList(); } if(!ids.contains(friendId)) { ids = new ArrayList<String>(ids); ids.add(friendId); collectionShareData.put(collectionId, Collections.unmodifiableList(ids)); dirty = true; return true; } else { return false; } } finally { lock.writeLock().unlock(); } } /** Removes a particular shareId from the given collection's Id. */ boolean removeFriendFromCollection(int collectionId, String friendId) { lock.writeLock().lock(); try { List<String> ids = collectionShareData.get(collectionId); if(ids != null && ids.contains(friendId)) { ids = new ArrayList<String>(ids); ids.remove(friendId); collectionShareData.put(collectionId, Collections.unmodifiableList(ids)); dirty = true; return true; } else { return false; } } finally { lock.writeLock().unlock(); } } /** Returns all shareIds for the given collection id. */ List<String> getFriendsForCollection(int collectionId) { lock.readLock().lock(); try { List<String> ids = collectionShareData.get(collectionId); if(ids != null) { return Collections.unmodifiableList(new ArrayList<String>(ids)); } else { return Collections.emptyList(); } } finally { lock.readLock().unlock(); } } /** * Sets a new share id list for the given collection id. Returns null if no * change was performed because the lists were the same, otherwise returns * the list this replaced. */ List<String> setFriendsForCollection(int collectionId, List<String> newIds) { lock.writeLock().lock(); try { List<String> oldIds = collectionShareData.get(collectionId); if(oldIds == null) { oldIds = Collections.emptyList(); } // See if old & new are the same -- if so, don't bother. // (use a HashSet so that equality isn't order based) if(new HashSet<String>(oldIds).equals(newIds)) { return null; } else { if(newIds.isEmpty()) { collectionShareData.remove(collectionId); } else { newIds = Collections.unmodifiableList(new ArrayList<String>(newIds)); collectionShareData.put(collectionId, newIds); } dirty = true; return oldIds; } } finally { lock.writeLock().unlock(); } } boolean isProgramManagingAllowed() { return LibrarySettings.ALLOW_PROGRAMS.getValue(); } boolean isGnutellaDocumentSharingAllowed() { return LibrarySettings.ALLOW_DOCUMENT_GNUTELLA_SHARING.getValue(); } boolean isCollectionSmartAddEnabled(int id, Category category) { return false; // TODO: What's going on with this? } void setCollectionSmartAddEnabled(int collectionId, Category image, boolean enabled) { // TODO: What's going on with this? } private Collection<String> lowercase(Collection<String> extensions) { Set<String> exts = new HashSet<String>(extensions.size()); for(String string : extensions) { exts.add(string.toLowerCase(Locale.US)); } return exts; } private static class FileProperties implements Serializable { private static final long serialVersionUID = 767248414812908206L; private boolean gnutella; private Set<String> friends; @Override public String toString() { return "FileProperties: gnutella: " + gnutella + ", friends: " + friends; } } }