package com.limegroup.gnutella; import java.io.File; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.EnumMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.atomic.AtomicReference; import org.limewire.core.api.Category; import org.limewire.core.api.file.CategoryManager; import org.limewire.core.settings.LibrarySettings; import org.limewire.setting.StringArraySetting; import org.limewire.util.FileUtils; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableSortedSet; import com.google.inject.Singleton; @Singleton class CategoryManagerImpl implements CategoryManager { /** A secondary category class designed to deal with the fact that programs is split up. */ private enum InternalCategory { AUDIO(Category.AUDIO), VIDEO(Category.VIDEO), IMAGE(Category.IMAGE), DOCUMENT(Category.DOCUMENT), PROGRAM_OSX_LINUX(Category.PROGRAM), PROGRAM_WINDOWS(Category.PROGRAM), PROGRAM_ALL(Category.PROGRAM), OTHER(Category.OTHER), TORRENT(Category.TORRENT); private final Category category; InternalCategory(Category category) { this.category = category; } Category getCategory() { return category; } static InternalCategory fromCategory(Category category) { switch(category) { case AUDIO: return InternalCategory.AUDIO; case DOCUMENT: return InternalCategory.DOCUMENT; case IMAGE: return InternalCategory.IMAGE; case PROGRAM: return InternalCategory.PROGRAM_ALL; case VIDEO: return InternalCategory.VIDEO; case OTHER: return InternalCategory.OTHER; case TORRENT: return InternalCategory.TORRENT; default: throw new IllegalArgumentException(category.toString()); } } } /** Map between internal category && built in extensions. */ private final Map<InternalCategory, Collection<String>> builtInExtensionMap; /** Map between internal category && reference to combined extensions (built-in & remote) */ private final Map<InternalCategory, AtomicReference<Collection<String>>> extensionMap; /** Map between internal category && predicate that refers to combined extensions. */ private final Map<InternalCategory, Predicate<String>> predicateMap; /** Map between internal category && setting that is associated with that category. */ private final Map<InternalCategory, StringArraySetting> settingMap; CategoryManagerImpl() { builtInExtensionMap = new EnumMap<InternalCategory, Collection<String>>( InternalCategory.class); builtInExtensionMap.put(InternalCategory.DOCUMENT, ImmutableSortedSet.orderedBy( String.CASE_INSENSITIVE_ORDER).add("123", "abw", "accdb", "accde", "accdr", "accdt", "ans", "asc", "asp", "bdr", "chm", "css", "csv", "dat", "db", "dif", "diz", "doc", "docm", "docx", "dotm", "dotx", "dvi", "eml", "eps", "epsf", "fm", "grv", "gsa", "gts", "hlp", "htm", "html", "idb", "idx", "iif", "info", "js", "jsp", "kfl", "kwd", "latex", "lif", "lit", "log", "man", "mcw", "mht", "mhtml", "mny", "msg", "obi", "odp", "ods", "odt", "ofx", "one", "onepkg", "ost", "pages", "pdf", "php", "pot", "potm", "potx", "pps", "ppsm", "ppsx", "ppt", "pptm", "pptx", "ps", "pub", "qba", "qbb", "qdb", "qbi", "qbm", "qbw", "qbx", "qdf", "qel", "qfp", "qpd", "qph", "qmd", "qsd", "rtf", "scd", "sdc", "sdd", "sdp", "sdw", "shw", "sldx", "sxc", "sxd", "sxp", "sxw", "t01", "t02", "t03", "t04", "t05", "t06", "t07", "t08", "t09", "t98", "t99", "ta0", "ta1", "ta2", "ta3", "ta4", "ta5", "ta6", "ta7", "ta8", "ta9", "tax", "tax2008", "tex", "texi", "toc", "tsv", "tvl", "txf", "txt", "wk1", "wk3", "wk4", "wks", "wp", "wp5", "wpd", "wps", "wri", "xhtml", "xlam", "xls", "xlsb", "xlsm", "xlsx", "xltm", "xltx", "xml", "xsf", "xsn", "qfx", "qif", "bud", "ofc", "pst", "mbf", "mn1", "mn2", "mn3", "mn4", "mn5", "mn6", "mn7", "mn8", "mn9", "m10", "m11", "m12", "m13", "m14", "m15", "m16", "boe", "box", "bri", "cnm", "dbx", "eml", "emlx", "idb", "idx", "maildb", "mbg", "mbs", "mbx", "mht", "msb", "msf", "msg", "nws", "pmi", "pmm", "pmx", "tbb", "toc", "vfb", "zmc", "stw", "odm", "ott", "wpt").build()); builtInExtensionMap.put(InternalCategory.AUDIO, ImmutableSortedSet.orderedBy( String.CASE_INSENSITIVE_ORDER).add("mp3", "mpa", "mp1", "mpga", "mp2", "ra", "rm", "ram", "rmj", "wma", "wav", "m4a", "m4p", "lqt", "ogg", "med", "aif", "aiff", "aifc", "au", "snd", "s3m", "aud", "mid", "midi", "rmi", "mod", "kar", "ac3", "shn", "fla", "flac", "cda", "mka").build()); builtInExtensionMap.put(InternalCategory.VIDEO, ImmutableSortedSet.orderedBy( String.CASE_INSENSITIVE_ORDER).add("mpg", "mpeg", "mpe", "mng", "mpv", "m1v", "vob", "mpv2", "mp2v", "m2p", "m2v", "mpgv", "vcd", "mp4", "dv", "dvd", "div", "divx", "dvx", "smi", "smil", "rv", "rmm", "rmvb", "avi", "asf", "asx", "wmv", "qt", "mov", "fli", "flc", "flx", "flv", "wml", "vrml", "swf", "dcr", "jve", "nsv", "mkv", "ogm", "cdg", "srt", "sub", "flv").build()); builtInExtensionMap.put(InternalCategory.IMAGE, ImmutableSortedSet.orderedBy( String.CASE_INSENSITIVE_ORDER).add("gif", "png", "bmp", "jpg", "jpeg", "jpe", "jif", "jiff", "jfif", "tif", "tiff", "iff", "lbm", "ilbm", "mac", "drw", "pct", "img", "bmp", "dib", "rle", "ico", "ani", "icl", "cur", "emf", "wmf", "pcx", "pcd", "tga", "pic", "fig", "psd", "wpg", "dcx", "cpt", "mic", "pbm", "pnm", "ppm", "xbm", "xpm", "xwd", "sgi", "fax", "rgb", "ras").build()); builtInExtensionMap .put(InternalCategory.PROGRAM_OSX_LINUX, ImmutableSortedSet.orderedBy( String.CASE_INSENSITIVE_ORDER).add("app", "bin", "mdb", "sh", "csh", "awk", "pl", "rpm", "deb", "gz", "gzip", "z", "bz2", "zoo", "tar", "tgz", "taz", "shar", "hqx", "sit", "dmg", "7z", "jar", "zip", "nrg", "iso", "jnlp", "rar", "sh").build()); builtInExtensionMap.put(InternalCategory.PROGRAM_WINDOWS, ImmutableSortedSet.orderedBy( String.CASE_INSENSITIVE_ORDER).add("mdb", "exe", "zip", "jar", "cab", "msi", "msp", "arj", "rar", "ace", "lzh", "lha", "bin", "nrg", "iso", "jnlp", "bat", "lnk", "vbs").build()); builtInExtensionMap.put(InternalCategory.TORRENT, ImmutableSortedSet.orderedBy( String.CASE_INSENSITIVE_ORDER).add("torrent").build()); extensionMap = new EnumMap<InternalCategory, AtomicReference<Collection<String>>>(InternalCategory.class); for(InternalCategory category : InternalCategory.values()) { // Do it for everything other than OTHER if(category != InternalCategory.OTHER) { extensionMap.put(category, new AtomicReference<Collection<String>>()); } } predicateMap = new EnumMap<InternalCategory, Predicate<String>>(InternalCategory.class); // Add all predicates that are in the extension map for(Map.Entry<InternalCategory, AtomicReference<Collection<String>>> entry : extensionMap.entrySet()) { predicateMap.put(entry.getKey(), new CollectionPredicate(entry.getValue())); } // Then add one for OTHER too predicateMap.put(InternalCategory.OTHER, new Predicate<String>() { public boolean apply(String input) { // This would be a terrible implementation for other categories, // but for OTHER it is OK because all we can do is say, // "do i belong in another category?... if not -> other" return getCategoryForExtension(input) == Category.OTHER; }; }); settingMap = new EnumMap<InternalCategory, StringArraySetting>(InternalCategory.class); settingMap.put(InternalCategory.AUDIO, LibrarySettings.ADDITIONAL_AUDIO_EXTS); settingMap.put(InternalCategory.DOCUMENT, LibrarySettings.ADDITIONAL_DOCUMENT_EXTS); settingMap.put(InternalCategory.IMAGE, LibrarySettings.ADDITIONAL_IMAGE_EXTS); settingMap.put(InternalCategory.PROGRAM_OSX_LINUX, LibrarySettings.ADDITIONAL_PROGRAM_OSX_LINUX_EXTS); settingMap.put(InternalCategory.PROGRAM_WINDOWS, LibrarySettings.ADDITIONAL_PROGRAM_WINDOWS_EXTS); settingMap.put(InternalCategory.VIDEO, LibrarySettings.ADDITIONAL_VIDEO_EXTS); settingMap.put(InternalCategory.TORRENT, LibrarySettings.ADDITIONAL_TORRENT_EXTS); rebuildExtensions(); } /** Rebuilds all extensions so that they contain both built-in & simpp extensions. */ private void rebuildExtensions() { // Redo every category that has a setting associated with it. for(Map.Entry<InternalCategory, StringArraySetting> entry : settingMap.entrySet()) { InternalCategory category = entry.getKey(); StringArraySetting remote = entry.getValue(); AtomicReference<Collection<String>> combinedMap = extensionMap.get(category); assert combinedMap != null && remote != null && category != null; combinedMap.set(combineAndCleanup(category, remote.get())); } // And then rebuild the combined PROGRAM category that has both. extensionMap.get(InternalCategory.PROGRAM_ALL).set( ImmutableSortedSet.orderedBy(String.CASE_INSENSITIVE_ORDER) .addAll(extensionMap.get(InternalCategory.PROGRAM_OSX_LINUX).get()) .addAll(extensionMap.get(InternalCategory.PROGRAM_WINDOWS).get()).build()); } private Collection<String> combineAndCleanup(InternalCategory category, String[] remote) { Set<String> remoteSet = new TreeSet<String>(Arrays.asList(remote)); // remove everything that's built-in for(Collection<String> builtIn : builtInExtensionMap.values()) { remoteSet.removeAll(builtIn); } // Remove the stuff from the other remote extensions, // otherwise we can end up with two different categories having // the same extension for(Map.Entry<InternalCategory, StringArraySetting> entry : settingMap.entrySet()) { // If our category doesn't match the setting's category, then remove // all extensions from that setting. if(category.getCategory() != entry.getKey().getCategory()) { remoteSet.removeAll(Arrays.asList(entry.getValue().get())); } } Collection<String> builtIn = builtInExtensionMap.get(category); assert builtIn != null; // Build the combined map by using the built in & the remaining extensions return ImmutableSortedSet.orderedBy(String.CASE_INSENSITIVE_ORDER). addAll(builtIn).addAll(remoteSet).build(); } @Override public Category getCategoryForExtension(String extension) { // note: the extension sets are all case insensitive, // so... no lowercasing required. for(Map.Entry<InternalCategory, AtomicReference<Collection<String>>> entry : extensionMap.entrySet()) { Collection<String> collection = entry.getValue().get(); if(collection.contains(extension)) { return entry.getKey().getCategory(); } } // If it matched nothing, it matches OTHER. return Category.OTHER; } @Override public Category getCategoryForFilename(String filename) { String extension = FileUtils.getFileExtension(filename); return getCategoryForExtension(extension); } @Override public Category getCategoryForFile(File file) { // note: the extension sets are all case insensitive, // so... no lowercasing required. String extension = FileUtils.getFileExtension(file); return getCategoryForExtension(extension); } @Override public Collection<String> getExtensionsForCategory(Category category) { AtomicReference<Collection<String>> ref = extensionMap.get(InternalCategory.fromCategory(category)); if(ref != null) { return ref.get(); } else { assert category == Category.OTHER; return Collections.emptySet(); } } @Override public Predicate<String> getExtensionFilterForCategory(Category category) { return predicateMap.get(InternalCategory.fromCategory(category)); } @Override public Predicate<String> getOsxAndLinuxProgramsFilter() { return predicateMap.get(InternalCategory.PROGRAM_OSX_LINUX); } @Override public Predicate<String> getWindowsProgramsFilter() { return predicateMap.get(InternalCategory.PROGRAM_WINDOWS); } private static final class CollectionPredicate implements Predicate<String> { private final AtomicReference<Collection<String>> delegate; public CollectionPredicate(AtomicReference<Collection<String>> set) { this.delegate = set; } @Override public boolean apply(String input) { return delegate.get().contains(input); } } @Override public boolean containsCategory(Category category, List<String> paths) { if (paths == null) { return false; } for ( String path : paths ) { if (getCategoryForFilename(path) == category) { return true; } } return false; } }