package org.limewire.core.impl.download; import java.awt.EventQueue; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.File; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.limewire.collection.glazedlists.GlazedListsFactory; import org.limewire.core.api.Category; import org.limewire.core.api.URN; import org.limewire.core.api.download.DownloadException; import org.limewire.core.api.download.DownloadItem; import org.limewire.core.api.download.DownloadListManager; import org.limewire.core.api.download.DownloadState; import org.limewire.core.api.download.DownloadItem.DownloadItemType; import org.limewire.core.api.file.CategoryManager; import org.limewire.core.api.magnet.MagnetLink; import org.limewire.core.api.search.Search; import org.limewire.core.api.search.SearchResult; import org.limewire.core.api.spam.SpamManager; import org.limewire.core.impl.download.listener.ItunesDownloadListenerFactory; import org.limewire.core.impl.download.listener.RecentDownloadListener; import org.limewire.core.impl.download.listener.TorrentDownloadListenerFactory; import org.limewire.core.impl.magnet.MagnetLinkImpl; import org.limewire.core.impl.search.CoreSearch; import org.limewire.core.impl.search.RemoteFileDescAdapter; import org.limewire.core.settings.SharingSettings; import org.limewire.inject.EagerSingleton; import org.limewire.io.Address; import org.limewire.io.GUID; import org.limewire.io.IpPort; import org.limewire.io.IpPortSet; import org.limewire.lifecycle.ServiceScheduler; import org.limewire.listener.SwingSafePropertyChangeSupport; import org.limewire.setting.FileSetting; import ca.odell.glazedlists.BasicEventList; import ca.odell.glazedlists.EventList; import ca.odell.glazedlists.GlazedLists; import ca.odell.glazedlists.ObservableElementList; import ca.odell.glazedlists.impl.ThreadSafeList; import com.google.inject.Inject; import com.google.inject.name.Named; import com.limegroup.gnutella.DownloadManager; import com.limegroup.gnutella.Downloader; import com.limegroup.gnutella.RemoteFileDesc; import com.limegroup.gnutella.browser.MagnetOptions; import com.limegroup.gnutella.downloader.RemoteFileDescFactory; import com.limegroup.gnutella.malware.VirusDefinitionDownloader; @EagerSingleton public class CoreDownloadListManager implements DownloadListManager { private final EventList<DownloadItem> observableDownloadItems; private EventList<DownloadItem> swingThreadDownloadItems; private final DownloadManager downloadManager; private final RemoteFileDescFactory remoteFileDescFactory; private final SpamManager spamManager; private final ItunesDownloadListenerFactory itunesDownloadListenerFactory; private final TorrentDownloadListenerFactory torrentDownloadListenerFactory; private final CoreDownloadItem.Factory coreDownloadItemFactory; private final PropertyChangeSupport changeSupport = new SwingSafePropertyChangeSupport(this); /**the base list - all removing and adding must be done from here.*/ private final ThreadSafeList<DownloadItem> threadSafeDownloadItems; private static final int PERIOD = 1000; private Map<org.limewire.core.api.URN, DownloadItem> urnMap = Collections.synchronizedMap(new HashMap<org.limewire.core.api.URN, DownloadItem>()); private final DownloadItemFactoryRegistry downloadItemFactoryRegistry; private final CategoryManager categoryManager; @Inject public CoreDownloadListManager(DownloadManager downloadManager, RemoteFileDescFactory remoteFileDescFactory, SpamManager spamManager, ItunesDownloadListenerFactory itunesDownloadListenerFactory, TorrentDownloadListenerFactory torrentDownloadListenerFactory, CoreDownloadItem.Factory coreDownloadItemFactory, DownloadItemFactoryRegistry downloadItemFactoryRegistry, CategoryManager categoryManager) { this.downloadManager = downloadManager; this.remoteFileDescFactory = remoteFileDescFactory; this.spamManager = spamManager; this.itunesDownloadListenerFactory = itunesDownloadListenerFactory; this.torrentDownloadListenerFactory = torrentDownloadListenerFactory; this.coreDownloadItemFactory = coreDownloadItemFactory; this.downloadItemFactoryRegistry = downloadItemFactoryRegistry; this.categoryManager = categoryManager; threadSafeDownloadItems = GlazedListsFactory.threadSafeList(new BasicEventList<DownloadItem>()); ObservableElementList.Connector<DownloadItem> downloadConnector = GlazedLists.beanConnector(DownloadItem.class); observableDownloadItems = GlazedListsFactory.observableElementList(threadSafeDownloadItems, downloadConnector); } @Inject void registerDownloadListener(DownloadListenerList listenerList) { listenerList.addDownloadListener(new CoreDownloadListener(threadSafeDownloadItems, new QueueTimeCalculator(observableDownloadItems))); } @Inject void registerService(ServiceScheduler scheduler, @Named("backgroundExecutor") ScheduledExecutorService backgroundExecutor) { Runnable command = new Runnable() { @Override public void run() { update(); } }; scheduler.scheduleWithFixedDelay("UI Download Status Monitor", command, PERIOD*2, PERIOD, TimeUnit.MILLISECONDS, backgroundExecutor); } // forces refresh private void update() { observableDownloadItems.getReadWriteLock().writeLock().lock(); try { // TODO use TransactionList for these for performance (requires using GlazedLists from head) for (DownloadItem item : observableDownloadItems) { if ((!item.getState().isFinished()) && item instanceof CoreDownloadItem) ((CoreDownloadItem) item).fireDataChanged(); } } finally { observableDownloadItems.getReadWriteLock().writeLock().unlock(); } } @Override public EventList<DownloadItem> getDownloads() { return observableDownloadItems; } @Override public EventList<DownloadItem> getSwingThreadSafeDownloads() { assert EventQueue.isDispatchThread(); if(swingThreadDownloadItems == null) { swingThreadDownloadItems = GlazedListsFactory.swingThreadProxyEventList(observableDownloadItems); } return swingThreadDownloadItems; } @Override public DownloadItem addDownload(Search search, List<? extends SearchResult> searchResults) throws DownloadException { return addDownload(search, searchResults, null, false); } @Override public DownloadItem addDownload(Search search, List<? extends SearchResult> searchResults, File saveFile, boolean overwrite) throws DownloadException { // do it before spam marking, since those search results might not support spam marking DownloadItem downloadItem = downloadItemFactoryRegistry.create(search, searchResults, saveFile, overwrite); if (downloadItem != null) { return downloadItem; } // Train the spam filter even if the results weren't rated as spam spamManager.handleUserMarkedGood(searchResults); RemoteFileDesc[] files; List<RemoteFileDesc> alts = new ArrayList<RemoteFileDesc>(); files = createRfdsAndAltsFromSearchResults(searchResults, alts); GUID queryGUID = null; if(search != null && search instanceof CoreSearch) { queryGUID = ((CoreSearch)search).getQueryGuid(); } Category category = searchResults.iterator().next().getCategory(); return createDownloader(files, alts, queryGUID, saveFile, overwrite, category); } private DownloadItem createDownloader(RemoteFileDesc[] files, List<RemoteFileDesc> alts, GUID queryGuid, File saveFile, boolean overwrite, Category category) throws DownloadException { File saveDir = null; String fileName = null; if(saveFile != null) { if(saveFile.isDirectory()) { saveDir = saveFile; } else { saveDir = saveFile.getParentFile(); fileName = saveFile.getName(); } } // determine per mediatype directory if saveLocation == null // and only pass it through if directory is different from default // save directory == !isDefault() //if (saveDir == null &&) { FileSetting fs = SharingSettings.getFileSettingForCategory(category); if (!fs.isDefault()) { saveDir = fs.get(); } // } Downloader downloader = downloadManager.download(files, alts, queryGuid, overwrite, saveDir, fileName); // This should have been funneled through our addDownload callback method, which // should have set the CoreDownloadItem. return (DownloadItem)downloader.getAttribute(DownloadItem.DOWNLOAD_ITEM); } private RemoteFileDesc[] createRfdsAndAltsFromSearchResults( List<? extends SearchResult> searchResults, List<RemoteFileDesc> altList) { List<RemoteFileDesc> rfds = new ArrayList<RemoteFileDesc>(searchResults.size()); Set<IpPort> alts = new IpPortSet(); // size of searchResults can change, so iterate over list and make no // assumptions about its size for (SearchResult result : searchResults) { RemoteFileDescAdapter rfdAdapter = (RemoteFileDescAdapter) result; rfds.add(rfdAdapter.getRfd()); alts.addAll(rfdAdapter.getAlts()); } // Iterate through RFDs and remove matching alts. // Also store the first SHA1 capable RFD for collecting alts. RemoteFileDesc sha1RFD = null; for(RemoteFileDesc next : rfds) { if(next.getSHA1Urn() != null) sha1RFD = next; Address address = next.getAddress(); // response alts are always only ip ports and no kind of other address // so it suffices to compare ip port instances of rfd addresses with the alt set // since other address types won't match anyways if (address instanceof IpPort) alts.remove(address); // Removes an alt that matches the IpPort of the RFD } // If no SHA1 rfd, just use the first. if(sha1RFD == null) sha1RFD = rfds.get(0); // Now iterate through alts & add more rfds. for(IpPort next : alts) { altList.add(remoteFileDescFactory.createRemoteFileDesc(sha1RFD, next)); } return rfds.toArray(new RemoteFileDesc[rfds.size()]); } /** * Adds the specified listener to the list that is notified when a * property value changes. Listeners added from the Swing UI thread will * always receive notification events on the Swing UI thread. */ @Override public void addPropertyChangeListener(PropertyChangeListener listener) { changeSupport.addPropertyChangeListener(listener); } /** * Removes the specified listener from the list that is notified when a * property value changes. */ @Override public void removePropertyChangeListener(PropertyChangeListener listener) { changeSupport.removePropertyChangeListener(listener); } /** * Checks for downloads in progress, and fires a property change event if * all downloads are completed. */ @Override public void updateDownloadsCompleted() { if (downloadManager.downloadsInProgress() == 0) { changeSupport.firePropertyChange(DOWNLOADS_COMPLETED, false, true); } } class CoreDownloadListener implements DownloadListener { private final List<DownloadItem> list; private final QueueTimeCalculator queueTimeCalculator; public CoreDownloadListener(List<DownloadItem> list, QueueTimeCalculator queueTimeCalculator){ this.list = list; this.queueTimeCalculator = queueTimeCalculator; } @Override public void downloadAdded(Downloader downloader) { // don't show currentDefinitionAVGCheck if(isVirusScannerCurrentInfoDownload(downloader)) return; //Save the starting time if it hasn't been set if(downloader.getAttribute(DownloadItem.DOWNLOAD_START_DATE)== null){ downloader.setAttribute(DownloadItem.DOWNLOAD_START_DATE, new Date(), true); } DownloadItem item = coreDownloadItemFactory.create(downloader, queueTimeCalculator); downloader.setAttribute(DownloadItem.DOWNLOAD_ITEM, item, false); if (categoryManager.getCategoryForFile(downloader.getFile()) == Category.TORRENT) { downloader.addListener(torrentDownloadListenerFactory.createListener(downloader, list)); } downloader.addListener(new RecentDownloadListener(downloader)); downloader.addListener(itunesDownloadListenerFactory.createListener(downloader)); threadSafeDownloadItems.add(item); changeSupport.firePropertyChange(DOWNLOAD_ADDED, false, true); URN urn = item.getUrn(); if(urn != null) { //the bittorrent File Downloader can have a null urn urnMap.put(item.getUrn(), item); } } @Override public void downloadRemoved(Downloader downloader) { // don't show currentDefinitionAVGCheck if(isVirusScannerCurrentInfoDownload(downloader)) return; DownloadItem item = getDownloadItem(downloader); DownloadState state = item.getState(); if (state.isFinished()) { changeSupport.firePropertyChange(DOWNLOAD_COMPLETED, null, item); } // Always remove anti-virus update item. For all others, remove // from list UNLESS (a) download is in error state, (b) download // is removed for protection, or (c) download is finished and // auto-clear is not set. if (item.getDownloadItemType() == DownloadItemType.ANTIVIRUS) { remove(item); } else if (state != DownloadState.ERROR && state != DownloadState.DANGEROUS && state != DownloadState.THREAT_FOUND && (SharingSettings.CLEAR_DOWNLOAD.getValue() || !state.isFinished())) { remove(item); } } @Override public void downloadsCompleted() { changeSupport.firePropertyChange(DOWNLOADS_COMPLETED, false, true); } DownloadItem getDownloadItem(Downloader downloader) { DownloadItem item = (DownloadItem)downloader.getAttribute(DownloadItem.DOWNLOAD_ITEM); return item; } /** * Returns true if this is a VirusDefinitionCheck download, false otherwise. * This download checks if Virus Definitions are up to date. No need * to show this to the user as this will usually return true. If this download * returns false, the user will see an AVG update download. */ private boolean isVirusScannerCurrentInfoDownload(Downloader downloader) { if(downloader instanceof VirusDefinitionDownloader) { VirusDefinitionDownloader virusDownload = (VirusDefinitionDownloader) downloader; if(virusDownload.getFile().getName().equals("current.nfo")) return true; } return false; } } @Override public DownloadItem addTorrentDownload(URI uri, boolean overwrite) throws DownloadException { Downloader downloader = downloadManager.downloadTorrent(uri, overwrite); return (DownloadItem)downloader.getAttribute(DownloadItem.DOWNLOAD_ITEM); } @Override public DownloadItem addTorrentDownload(String name, URN sha1, List<URI> trackers) throws DownloadException { Downloader downloader = downloadManager.downloadTorrent(name, (com.limegroup.gnutella.URN)sha1, trackers); return (DownloadItem)downloader.getAttribute(DownloadItem.DOWNLOAD_ITEM); } @Override public DownloadItem addDownload(MagnetLink magnet, File saveFile, boolean overwrite) throws DownloadException { File saveDir = null; String fileName = null; if(saveFile != null) { if(saveFile.isDirectory()) { saveDir = saveFile; } else { saveDir = saveFile.getParentFile(); fileName = saveFile.getName(); } } MagnetOptions magnetOptions = ((MagnetLinkImpl)magnet).getMagnetOptions(); Downloader downloader = downloadManager.download(magnetOptions, overwrite, saveDir, fileName); DownloadItem downloadItem = (DownloadItem)downloader.getAttribute(DownloadItem.DOWNLOAD_ITEM); return downloadItem; } @Override public DownloadItem addTorrentDownload(File file, File saveDirectory, boolean overwrite) throws DownloadException { Downloader downloader = downloadManager.downloadTorrent(file, saveDirectory, overwrite); return (DownloadItem)downloader.getAttribute(DownloadItem.DOWNLOAD_ITEM); } @Override public boolean contains(org.limewire.core.api.URN urn) { return urnMap.containsKey(urn); } @Override public DownloadItem getDownloadItem(org.limewire.core.api.URN urn) { return urnMap.get(urn); } @Override public void clearFinished() { final List<DownloadItem> finishedItems = new ArrayList<DownloadItem>(); threadSafeDownloadItems.getReadWriteLock().writeLock().lock(); try { for (DownloadItem item : threadSafeDownloadItems) { DownloadState state = item.getState(); if (state.isFinished()) { finishedItems.add(item); } } for(DownloadItem item : finishedItems) { remove(item); } } finally { threadSafeDownloadItems.getReadWriteLock().writeLock().unlock(); } } @Override public void remove(DownloadItem item) { urnMap.remove(item.getUrn()); threadSafeDownloadItems.remove(item); } }