/*
* Copyright 2004 - 2008 Christian Sprajc. All rights reserved.
*
* This file is part of PowerFolder.
*
* PowerFolder is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation.
*
* PowerFolder is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PowerFolder. If not, see <http://www.gnu.org/licenses/>.
*
* $Id$
*/
package de.dal33t.powerfolder.ui.information.downloads;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.TimerTask;
import javax.swing.SwingUtilities;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;
import de.dal33t.powerfolder.PFComponent;
import de.dal33t.powerfolder.event.TransferManagerAdapter;
import de.dal33t.powerfolder.event.TransferManagerEvent;
import de.dal33t.powerfolder.transfer.Download;
import de.dal33t.powerfolder.transfer.DownloadManager;
import de.dal33t.powerfolder.transfer.TransferManager;
import de.dal33t.powerfolder.transfer.TransferProblem;
import de.dal33t.powerfolder.ui.model.SortedTableModel;
import de.dal33t.powerfolder.ui.model.TransferManagerModel;
import de.dal33t.powerfolder.ui.util.UIUtil;
import de.dal33t.powerfolder.util.Reject;
import de.dal33t.powerfolder.util.Translation;
import de.dal33t.powerfolder.util.compare.DownloadManagerComparator;
import de.dal33t.powerfolder.util.compare.ReverseComparator;
import de.dal33t.powerfolder.util.compare.TransferComparator;
/**
* A Tablemodel adapter which acts upon a transfermanager.
*
* @author <a href="mailto:totmacher@powerfolder.com">Christian Sprajc </a>
* @version $Revision: 1.11.2.1 $
*/
public class DownloadManagersTableModel extends PFComponent implements
TableModel, SortedTableModel
{
public static final int COLTYPE = 0;
public static final int COLFILE = 1;
public static final int COLPROGRESS = 2;
public static final int COLSIZE = 3;
public static final int COLFOLDER = 4;
public static final int COLFROM = 5;
private static final int UPDATE_TIME = 1000;
private final Collection<TableModelListener> listeners;
private final List<DownloadManager> downloadManagers;
private int fileInfoComparatorType = -1;
private boolean sortAscending = true;
private volatile boolean dirty;
private int sortColumn;
private final TransferManagerModel model;
private volatile boolean periodicUpdate;
public DownloadManagersTableModel(TransferManagerModel model) {
super(model.getController());
this.model = model;
Reject.ifNull(model, "Model is null");
listeners = new LinkedList<TableModelListener>();
downloadManagers = new ArrayList<DownloadManager>();
// Add listener
model.getTransferManager().addListener(new MyTransferManagerListener());
periodicUpdate = false;
MyTimerTask task = new MyTimerTask();
getController().scheduleAndRepeat(task, UPDATE_TIME);
}
/**
* Initalizes the model upon a transfer manager
*
* @param tm
*/
public void initialize() {
TransferManager tm = model.getTransferManager();
for (DownloadManager downloadManager : tm
.getCompletedDownloadsCollection())
{
if (!isMetaFolderDownload(downloadManager)) {
downloadManagers.add(downloadManager);
}
}
for (DownloadManager downloadManager : tm.getActiveDownloads()) {
if (!isMetaFolderDownload(downloadManager)) {
downloadManagers.add(downloadManager);
}
}
dirty = true;
// #1732 FIXME: Does not work addAll(tm.getPendingDownloads());
}
/**
* UI does not care about metaFolder events.
*
* @param downloadManager
* @return
*/
private static boolean isMetaFolderDownload(DownloadManager downloadManager) {
return downloadManager.getFileInfo().getFolderInfo().isMetaFolder();
}
public boolean isPeriodicUpdate() {
return periodicUpdate;
}
public void setPeriodicUpdate(boolean periodicUpdate) {
// Transition no update -> update.
if (!this.periodicUpdate && periodicUpdate) {
resortAndUpdate();
}
this.periodicUpdate = periodicUpdate;
}
/**
* @param rowIndex
* @return the download at the specified download row. Or null if the
* rowIndex exceeds the table rows
*/
public DownloadManager getDownloadManagerAtRow(int rowIndex) {
synchronized (downloadManagers) {
if (rowIndex >= 0 && rowIndex < downloadManagers.size()) {
return downloadManagers.get(rowIndex);
}
}
return null;
}
public DownloadManager[] getDownloadManagersAtRows(int[] ints) {
DownloadManager[] rows = new DownloadManager[ints.length];
synchronized (downloadManagers) {
int x = 0;
for (int i : ints) {
if (i < downloadManagers.size()) {
rows[x] = downloadManagers.get(i);
}
x++;
}
}
return rows;
}
public boolean sortBy(int columnIndex) {
sortColumn = columnIndex;
switch (columnIndex) {
case COLTYPE :
return sortMe(TransferComparator.BY_EXT);
case COLFILE :
return sortMe(TransferComparator.BY_FILE_NAME);
case COLPROGRESS :
return sortMe(TransferComparator.BY_PROGRESS);
case COLSIZE :
return sortMe(TransferComparator.BY_SIZE);
case COLFOLDER :
return sortMe(TransferComparator.BY_FOLDER);
case COLFROM :
return sortMe(TransferComparator.BY_MEMBER);
}
sortColumn = -1;
return false;
}
/**
* Re-sorts the file list with the new comparator only if comparator differs
* from old one
*
* @param newComparator
* @return if the table was freshly sorted
*/
public boolean sortMe(int newComparatorType) {
int oldComparatorType = fileInfoComparatorType;
fileInfoComparatorType = newComparatorType;
if (oldComparatorType != newComparatorType) {
boolean sorted = sort();
if (sorted) {
fireModelChanged();
return true;
}
}
return false;
}
private boolean sort() {
if (fileInfoComparatorType != -1) {
DownloadManagerComparator comparator = new DownloadManagerComparator(
fileInfoComparatorType);
if (sortAscending) {
Collections.sort(downloadManagers, comparator);
} else {
Collections.sort(downloadManagers, new ReverseComparator(
comparator));
}
return true;
}
return false;
}
private void fireModelChanged() {
Runnable runner = new Runnable() {
public void run() {
TableModelEvent e = new TableModelEvent(
DownloadManagersTableModel.this);
for (Object aTableListener : listeners) {
TableModelListener listener = (TableModelListener) aTableListener;
listener.tableChanged(e);
}
}
};
UIUtil.invokeLaterInEDT(runner);
}
public void reverseList() {
sortAscending = !sortAscending;
synchronized (downloadManagers) {
Collections.reverse(downloadManagers);
}
fireModelChanged();
dirty = true;
}
public int getColumnCount() {
return 6;
}
public int getRowCount() {
return downloadManagers.size();
}
public String getColumnName(int columnIndex) {
switch (columnIndex) {
case COLTYPE :
return "";
case COLFILE :
return Translation.getTranslation("general.file");
case COLPROGRESS :
return Translation.getTranslation("transfers.progress");
case COLSIZE :
return Translation.getTranslation("general.size");
case COLFOLDER :
return Translation.getTranslation("general.folder");
case COLFROM :
return Translation.getTranslation("transfers.from");
}
return null;
}
public Class<DownloadManager> getColumnClass(int columnIndex) {
return DownloadManager.class;
}
public Object getValueAt(int rowIndex, int columnIndex) {
if (rowIndex >= downloadManagers.size()) {
logSevere("Illegal rowIndex requested. rowIndex " + rowIndex
+ ", downloadManagers " + downloadManagers.size());
return null;
}
DownloadManager downloadManager = downloadManagers.get(rowIndex);
if (downloadManager == null) {
logSevere("Illegal rowIndex requested. rowIndex " + rowIndex
+ ", downloadManagers " + downloadManagers.size());
return null;
}
switch (columnIndex) {
case COLTYPE :
case COLFILE :
return downloadManager.getFileInfo();
case COLPROGRESS :
return downloadManager;
case COLSIZE :
return downloadManager.getFileInfo().getSize();
case COLFOLDER :
return downloadManager.getFileInfo().getFolderInfo();
case COLFROM :
return downloadManager.getSources();
}
return null;
}
public boolean isCellEditable(int rowIndex, int columnIndex) {
return false;
}
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
throw new IllegalStateException(
"Unable to set value in DownloadTableModel, not editable");
}
public void addTableModelListener(TableModelListener l) {
listeners.add(l);
}
public void removeTableModelListener(TableModelListener l) {
listeners.remove(l);
}
public int getSortColumn() {
return sortColumn;
}
public boolean isSortAscending() {
return sortAscending;
}
/**
* Tells listeners, that a new row at the end of the table has been added
*/
private void rowAdded() {
TableModelEvent e = new TableModelEvent(this, getRowCount(),
getRowCount(), TableModelEvent.ALL_COLUMNS, TableModelEvent.INSERT);
modelChanged(e);
}
private void rowRemoved(int row) {
TableModelEvent e = new TableModelEvent(this, row, row,
TableModelEvent.ALL_COLUMNS, TableModelEvent.DELETE);
modelChanged(e);
}
private void rowsUpdated(int start, int end) {
TableModelEvent e = new TableModelEvent(this, start, end);
modelChanged(e);
}
/**
* fire change on whole model
*/
private void rowsUpdatedAll() {
rowsUpdated(0, downloadManagers.size());
}
/**
* Fires an modelevent to all listeners, that model has changed
*/
private void modelChanged(final TableModelEvent e) {
// log().verbose("Download tablemodel changed");
Runnable runner = new Runnable() {
public void run() {
synchronized (listeners) {
for (TableModelListener listener : listeners) {
listener.tableChanged(e);
}
}
}
};
UIUtil.invokeLaterInEDT(runner);
}
/**
* Only some types of problem are relevant for display.
* <p>
* TODO COPIED to TransferTableCellRenderer
*
* @param problem
* the transfer problem
* @return true if it should be displayed.
*/
private static boolean shouldShowProblem(TransferProblem problem) {
return TransferProblem.FILE_NOT_FOUND_EXCEPTION.equals(problem)
|| TransferProblem.IO_EXCEPTION.equals(problem)
|| TransferProblem.TEMP_FILE_DELETE.equals(problem)
|| TransferProblem.TEMP_FILE_OPEN.equals(problem)
|| TransferProblem.TEMP_FILE_WRITE.equals(problem)
|| TransferProblem.MD5_ERROR.equals(problem);
}
/**
* Removes one download from the model and fires the tablemodel event
*
* @param download
*/
private void removeDownload(Download download,
boolean forceRemoveDownloadManager)
{
if (download.getFile().getFolderInfo().isMetaFolder()
|| isMetaFolderDownload(download.getDownloadManager()))
{
return;
}
int index = downloadManagers.indexOf(download.getDownloadManager());
if (index >= 0) {
if (!download.getDownloadManager().hasSources()
|| forceRemoveDownloadManager)
{
downloadManagers.remove(index);
rowRemoved(index);
} else {
rowsUpdated(index, index);
}
dirty = true;
} else {
logSevere("Unable to remove download from tablemodel, not found: "
+ download);
}
}
public void setAscending(boolean ascending) {
this.sortAscending = ascending;
}
// /////////////////
// Inner Classes //
// /////////////////
/**
* Listener on Transfer manager with new event system
*
* @author <a href="mailto:totmacher@powerfolder.com">Christian Sprajc </a>
*/
private class MyTransferManagerListener extends TransferManagerAdapter {
public void downloadRequested(TransferManagerEvent event) {
addOrUpdateDownload(event.getDownload());
}
public void downloadQueued(TransferManagerEvent event) {
addOrUpdateDownload(event.getDownload());
}
public void downloadStarted(TransferManagerEvent event) {
addOrUpdateDownload(event.getDownload());
}
public void downloadAborted(TransferManagerEvent event) {
if (event.getDownload() == null) {
return;
}
if (event.getDownload().isCompleted()) {
return;
}
removeDownload(event.getDownload(), false);
}
public void downloadBroken(TransferManagerEvent event) {
if (event.getDownload() == null) {
return;
}
if (event.getDownload().isCompleted()) {
return;
}
if (shouldShowProblem(event.getDownload().getTransferProblem())) {
addOrUpdateDownload(event.getDownload());
} else if (event.getDownload().isRequestedAutomatic()) {
removeDownload(event.getDownload(), false);
}
}
public void downloadCompleted(TransferManagerEvent event) {
addOrUpdateDownload(event.getDownload());
}
public void completedDownloadRemoved(TransferManagerEvent event) {
removeDownload(event.getDownload(), true);
}
public void pendingDownloadEnqueued(TransferManagerEvent event) {
// #1732 FIXME
// addOrUpdateDownload(event.getDownload());
}
/**
* Searches downloads for a download with identical FileInfo.
*
* @param dl
* download to search for identical copy
* @return index of the download with identical FileInfo, -1 if not
* found
*/
private int findDownloadIndex(Download dl) {
return downloadManagers.indexOf(dl.getDownloadManager());
// for (int i = 0; i < downloadManagers.size(); i++) {
// DownloadManager downloadManager = downloadManagers.get(i);
// if (downloadManager == null) {
// // Skip
// continue;
// }
// if (downloadManager.equals(dl.getDownloadManager())) {
// return i;
// }
// // for (Download download : downloadManager.getSources()) {
// // if (download.getFile().isVersionDateAndSizeIdentical(
// // dl.getFile())
// // && (Util.equals(download.getPartner(), dl.getPartner()) ||
// // download
// // .isPending()))
// // {
// // return i;
// // }
// // }
// }
//
// // No match
// return -1;
}
private void addOrUpdateDownload(Download dl) {
if (isMetaFolderDownload(dl.getDownloadManager())) {
return;
}
int index = findDownloadIndex(dl);
DownloadManager alreadyDl = index >= 0 ? downloadManagers
.get(index) : null;
if (alreadyDl == null) {
downloadManagers.add(dl.getDownloadManager());
rowAdded();
} else {
// @todo DownloadManager already knows of change???
rowsUpdated(index, index);
}
dirty = true;
}
public boolean fireInEventDispatchThread() {
return true;
}
}
/**
* Continously updates the UI
*
* @author <a href="mailto:totmacher@powerfolder.com">Christian Sprajc </a>
*/
private class MyTimerTask extends TimerTask {
public void run() {
if (!periodicUpdate) {
// Skip
return;
}
resortAndUpdate();
}
}
private void resortAndUpdate() {
Runnable wrapper = new Runnable() {
public void run() {
dirty = dirty
|| getController().getTransferManager()
.countActiveDownloads() > 0;
if (dirty) {
if (fileInfoComparatorType == TransferComparator.BY_PROGRESS)
{
// Always sort on a PROGRESS change, so that the table
// reorders correctly.
sort();
}
rowsUpdatedAll();
}
dirty = false;
}
};
SwingUtilities.invokeLater(wrapper);
}
}