package org.limewire.ui.swing.downloads; import java.awt.event.ActionEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.swing.AbstractAction; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JPopupMenu; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import org.limewire.collection.glazedlists.GlazedListsFactory; import org.limewire.core.api.URN; 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.settings.SharingSettings; import org.limewire.inject.LazySingleton; import org.limewire.setting.evt.SettingEvent; import org.limewire.setting.evt.SettingListener; import org.limewire.ui.swing.components.HyperlinkButton; import org.limewire.ui.swing.downloads.table.DownloadStateExcluder; import org.limewire.ui.swing.downloads.table.DownloadStateMatcher; import org.limewire.ui.swing.settings.SwingUiSettings; import org.limewire.ui.swing.transfer.TransferTrayNavigator; import org.limewire.ui.swing.util.I18n; import org.limewire.ui.swing.util.SwingUtils; import org.limewire.util.FileUtils; import ca.odell.glazedlists.EventList; import ca.odell.glazedlists.SortedList; import ca.odell.glazedlists.event.ListEvent; import ca.odell.glazedlists.event.ListEventListener; import com.google.inject.Inject; import com.google.inject.Provider; @LazySingleton public class DownloadMediator { public static enum SortOrder {ORDER_ADDED, NAME, PROGRESS, TIME_REMAINING, SPEED, STATUS, FILE_TYPE, EXTENSION}; /** * unfiltered - common to all tables */ private final SortedList<DownloadItem> downloadsCommonBaseList; private final DownloadListManager downloadListManager; private final Provider<MainDownloadPanel> downloadPanelFactory; private final Provider<DownloadHeaderPopupMenu> headerPopupMenuFactory; private final Provider<TransferTrayNavigator> transferTrayNavigator; private EventList<DownloadItem> activeList; private JButton clearFinishedButton; private JButton fixStalledButton; private List<JButton> headerButtons; private DownloadHeaderPopupMenu headerPopupMenu; private final Set<SortOrder> sortInspectionSet = new HashSet<SortOrder>(); @Inject public DownloadMediator(DownloadListManager downloadManager, Provider<MainDownloadPanel> downloadPanelFactory, Provider<DownloadHeaderPopupMenu> headerPopupMenuFactory, Provider<TransferTrayNavigator> transferTrayNavigator) { this.downloadListManager = downloadManager; this.downloadPanelFactory = downloadPanelFactory; this.headerPopupMenuFactory = headerPopupMenuFactory; this.transferTrayNavigator = transferTrayNavigator; EventList<DownloadItem> baseList = GlazedListsFactory.filterList(downloadManager.getSwingThreadSafeDownloads(), new DownloadStateExcluder(DownloadState.CANCELLED)); downloadsCommonBaseList = GlazedListsFactory.sortedList(baseList, getSortComparator(getSortOrder(), isSortAscending())); } /** * Registers listeners to update state. */ @Inject void register() { // Add listener to display download table when download added. downloadListManager.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if (DownloadListManager.DOWNLOAD_ADDED.equals(evt.getPropertyName())) { transferTrayNavigator.get().selectDownloads(); } } }); // Add setting listener to clear finished downloads. When set, we // clear finished downloads and hide the "clear finished" button. SharingSettings.CLEAR_DOWNLOAD.addSettingListener(new SettingListener() { @Override public void settingChanged(SettingEvent evt) { SwingUtils.invokeNowOrLater(new Runnable() { @Override public void run() { boolean clearDownloads = SharingSettings.CLEAR_DOWNLOAD.getValue(); if (clearDownloads) { clearFinished(); } if (clearFinishedButton != null) { clearFinishedButton.setVisible(!clearDownloads); } } }); } }); // Add list listeners to enable/show header buttons. EventList<DownloadItem> doneList = GlazedListsFactory.filterList(getDownloadList(), new DownloadStateMatcher(DownloadState.DONE, DownloadState.DANGEROUS, DownloadState.THREAT_FOUND, DownloadState.SCAN_FAILED)); EventList<DownloadItem> stalledList = GlazedListsFactory.filterList(getDownloadList(), new DownloadStateMatcher(DownloadState.STALLED)); doneList.addListEventListener(new ListEventListener<DownloadItem>() { @Override public void listChanged(ListEvent<DownloadItem> listChanges) { if (clearFinishedButton != null) { clearFinishedButton.setEnabled(listChanges.getSourceList().size() > 0); } } }); stalledList.addListEventListener(new ListEventListener<DownloadItem>() { @Override public void listChanged(ListEvent<DownloadItem> listChanges) { if (fixStalledButton != null) { fixStalledButton.setVisible(listChanges.getSourceList().size() != 0); } } }); } public JComponent getComponent() { return downloadPanelFactory.get(); } public boolean isSortAscending() { return SwingUiSettings.DOWNLOAD_SORT_ASCENDING.getValue(); } public SortOrder getSortOrder() { try { String sortKey = SwingUiSettings.DOWNLOAD_SORT_KEY.get(); return SortOrder.valueOf(sortKey); } catch (IllegalArgumentException ex) { // Return default order if setting is invalid. return SortOrder.ORDER_ADDED; } } public void setSortOrder(SortOrder order, boolean isAscending){ // Save sort settings. SwingUiSettings.DOWNLOAD_SORT_KEY.set(order.toString()); SwingUiSettings.DOWNLOAD_SORT_ASCENDING.setValue(isAscending); // Apply sort order. downloadsCommonBaseList.setComparator(getSortComparator(order, isAscending)); sortInspectionSet.add(order); } /** * Returns a comparator for the specified sort key and direction. */ private Comparator<DownloadItem> getSortComparator(SortOrder sortOrder, boolean ascending) { Comparator<DownloadItem> comparator; switch (sortOrder) { case ORDER_ADDED: comparator = new OrderAddedComparator(); break; case NAME: comparator = new NameComparator(); break; case PROGRESS: comparator = new ProgressComparator(); break; case TIME_REMAINING: comparator = new TimeRemainingComparator(); break; case SPEED: comparator = new SpeedComparator(); break; case STATUS: comparator = new DownloadStateComparator(); break; case FILE_TYPE: comparator = new FileTypeComparator(); break; case EXTENSION: comparator = new FileExtensionComparator(); break; default: throw new IllegalArgumentException("Unknown SortOrder: " + sortOrder); } if (ascending) { return comparator; } else { return new DescendingComparator(comparator); } } public void pauseAll() { downloadsCommonBaseList.getReadWriteLock().writeLock().lock(); try { for (DownloadItem item : downloadsCommonBaseList) { if (item.getState().isPausable()) { item.pause(); } } } finally { downloadsCommonBaseList.getReadWriteLock().writeLock().unlock(); } } public void resumeAll() { downloadsCommonBaseList.getReadWriteLock().writeLock().lock(); try { for (DownloadItem item : downloadsCommonBaseList) { if (item.getState().isResumable()) { item.resume(); } } } finally { downloadsCommonBaseList.getReadWriteLock().writeLock().unlock(); } } /** * Returns a list of active download items. */ public EventList<DownloadItem> getActiveList() { if (activeList == null) { activeList = GlazedListsFactory.filterList(downloadsCommonBaseList, new DownloadStateExcluder(DownloadState.ERROR, DownloadState.DONE, DownloadState.CANCELLED, DownloadState.DANGEROUS, DownloadState.THREAT_FOUND, DownloadState.SCAN_FAILED)); } return activeList; } /** * Returns a sorted list of downloads. */ public EventList<DownloadItem> getDownloadList() { return downloadsCommonBaseList; } /** * Returns a list of header buttons. */ public List<JButton> getHeaderButtons() { if (headerButtons == null) { // Create buttons. fixStalledButton = new HyperlinkButton(new FixStalledAction()); fixStalledButton.setVisible(false); clearFinishedButton = new HyperlinkButton(new ClearFinishedAction()); clearFinishedButton.setEnabled(false); // Add buttons to list. headerButtons = new ArrayList<JButton>(); headerButtons.add(fixStalledButton); headerButtons.add(clearFinishedButton); } return headerButtons; } /** * Returns the header popup menu associated with the downloads table. */ public JPopupMenu getHeaderPopupMenu() { if (headerPopupMenu == null) { headerPopupMenu = headerPopupMenuFactory.get(); headerPopupMenu.addPopupMenuListener(new PopupMenuListener() { @Override public void popupMenuCanceled(PopupMenuEvent e) { headerPopupMenu.removeAll(); } @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { headerPopupMenu.removeAll(); } @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) { headerPopupMenu.populate(); } }); } return headerPopupMenu; } public void clearFinished() { downloadListManager.clearFinished(); } public void fixStalled() { List<DownloadItem> items = getMatchingDownloadItems(DownloadState.STALLED); for (DownloadItem item : items) { item.resume(); } } public void cancelStalled() { cancelMatchingDownloadItems(DownloadState.STALLED); } public void cancelError() { cancelMatchingDownloadItems(DownloadState.ERROR); } public void cancelAll() { cancelMatchingDownloadItems(null); } public boolean hasResumable() { downloadsCommonBaseList.getReadWriteLock().writeLock().lock(); try { for (DownloadItem item : downloadsCommonBaseList) { if(item.getState().isResumable()) return true; } } finally { downloadsCommonBaseList.getReadWriteLock().writeLock().unlock(); } return false; } public boolean hasPausable() { downloadsCommonBaseList.getReadWriteLock().writeLock().lock(); try { for (DownloadItem item : downloadsCommonBaseList) { if(item.getState().isPausable()) return true; } } finally { downloadsCommonBaseList.getReadWriteLock().writeLock().unlock(); } return false; } public boolean containsState(DownloadState state) { return getMatchingDownloadItems(state).size() > 0; } /** * * @param state The state of the DownloadItems to be canceled. Null will cancel all. */ private void cancelMatchingDownloadItems(DownloadState state){ List<DownloadItem> items = getMatchingDownloadItems(state); for(DownloadItem item : items){ item.cancel(); } } /** * * @param state null will return all DownloadItems * @return a List of all DownloadItems in the specified DownloadState */ private List<DownloadItem> getMatchingDownloadItems(DownloadState state) { if (state == null) { return new ArrayList<DownloadItem>(downloadsCommonBaseList); } List<DownloadItem> matchingItems = new ArrayList<DownloadItem>(); for (DownloadItem item : downloadsCommonBaseList) { if (item.getState() == state) { matchingItems.add(item); } } return matchingItems; } /** * Action to clear all finished downloads. */ private class ClearFinishedAction extends AbstractAction { public ClearFinishedAction() { super(I18n.tr("Clear Finished")); } @Override public void actionPerformed(ActionEvent e) { clearFinished(); } } /** * Action to fix all stalled downloads. */ private class FixStalledAction extends AbstractAction { public FixStalledAction() { super(I18n.tr("Fix Stalled")); } @Override public void actionPerformed(ActionEvent e) { fixStalled(); } } private static class OrderAddedComparator implements Comparator<DownloadItem>{ @Override public int compare(DownloadItem o1, DownloadItem o2) { if (o1 == o2){ return 0; } return o2.getStartDate().compareTo(o1.getStartDate()); } } private static class NameComparator implements Comparator<DownloadItem>{ @Override public int compare(DownloadItem o1, DownloadItem o2) { if (o1 == o2){ return 0; } return o1.getTitle().compareTo(o2.getTitle()); } } private static class ProgressComparator implements Comparator<DownloadItem>{ @Override public int compare(DownloadItem o1, DownloadItem o2) { if (o1 == o2){ return 0; } return o1.getPercentComplete() - o2.getPercentComplete(); } } private static class TimeRemainingComparator implements Comparator<DownloadItem>{ @Override public int compare(DownloadItem o1, DownloadItem o2) { if (o1 == o2){ return 0; } return (int)(o1.getRemainingDownloadTime() - o2.getRemainingDownloadTime()); } } private static class SpeedComparator implements Comparator<DownloadItem>{ @Override public int compare(DownloadItem o1, DownloadItem o2) { if (o1 == o2){ return 0; } return (int)o2.getDownloadSpeed() - (int)o1.getDownloadSpeed(); } } private static class FileTypeComparator implements Comparator<DownloadItem>{ @Override public int compare(DownloadItem o1, DownloadItem o2) { if (o1 == o2){ return 0; } return o1.getCategory().compareTo(o2.getCategory()); } } private static class FileExtensionComparator implements Comparator<DownloadItem> { @Override public int compare(DownloadItem o1, DownloadItem o2) { if (o1 == o2){ return 0; } return FileUtils.getFileExtension(o1.getDownloadingFile()).compareTo(FileUtils.getFileExtension(o2.getDownloadingFile())); } } private static class DescendingComparator implements Comparator<DownloadItem>{ private Comparator<DownloadItem> delegate; public DescendingComparator(Comparator<DownloadItem> delegate){ this.delegate = delegate; } @Override public int compare(DownloadItem o1, DownloadItem o2) { return -1 * delegate.compare(o1, o2); } } public void selectAndScrollTo(URN urn) { transferTrayNavigator.get().selectDownloads(); downloadPanelFactory.get().selectAndScrollTo(urn); } }