/*
* 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.uploads;
import java.awt.EventQueue;
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.Member;
import de.dal33t.powerfolder.PFComponent;
import de.dal33t.powerfolder.event.TransferManagerAdapter;
import de.dal33t.powerfolder.event.TransferManagerEvent;
import de.dal33t.powerfolder.light.FileInfo;
import de.dal33t.powerfolder.light.FolderInfo;
import de.dal33t.powerfolder.transfer.TransferManager;
import de.dal33t.powerfolder.transfer.Upload;
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.Translation;
import de.dal33t.powerfolder.util.Util;
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:schaatser@powerfolder.com">Jan van Oosterom</A>
* @version $Revision: 1.5.2.1 $
*/
public class UploadsTableModel extends PFComponent implements TableModel,
SortedTableModel
{
public static final int UPDATE_TIME = 1000;
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 COLTO = 5;
private Collection<TableModelListener> listeners;
private List<Upload> uploads;
private int fileInfoComparatorType = -1;
private boolean sortAscending = true;
private int sortColumn;
private final TransferManagerModel model;
private volatile boolean periodicUpdate;
/**
* Constructs a new table model for uploads.
*
* @param model
* the transfermanager model
*/
public UploadsTableModel(TransferManagerModel model) {
super(model.getController());
this.model = model;
listeners = new LinkedList<TableModelListener>();
uploads = new ArrayList<Upload>();
// Add listener
model.getTransferManager().addListener(
new UploadTransferManagerListener());
periodicUpdate = true;
MyTimerTask task = new MyTimerTask();
getController().scheduleAndRepeat(task, UPDATE_TIME);
}
/**
* Initializes the model upon a transfer manager
*/
public void initialize() {
TransferManager tm = model.getTransferManager();
for (Upload upload : tm.getActiveUploads()) {
if (!isMetaFolderUpload(upload)) {
uploads.add(upload);
}
}
for (Upload upload : tm.getQueuedUploads()) {
if (!isMetaFolderUpload(upload)) {
uploads.add(upload);
}
}
}
/**
* UI does not care about metaFolder events.
*
* @param upload
* @return
*/
private boolean isMetaFolderUpload(Upload upload) {
return upload.getFile().getFolderInfo().isMetaFolder();
}
// Public exposing ********************************************************
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 upload at the specified upload row
*/
public Upload getUploadAtRow(int rowIndex) {
if (rowIndex >= uploads.size() || rowIndex == -1) {
logSevere("Illegal rowIndex requested. rowIndex " + rowIndex
+ ", uploads " + uploads.size());
return null;
}
return uploads.get(rowIndex);
}
// Application logic ******************************************************
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 COLTO :
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 newComparatorType
* @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) {
TransferComparator comparator = new TransferComparator(
fileInfoComparatorType);
synchronized (uploads) {
if (sortAscending) {
Collections.sort(uploads, comparator);
} else {
Collections
.sort(uploads, new ReverseComparator(comparator));
}
}
return true;
}
return false;
}
private void fireModelChanged() {
Runnable runner = new Runnable() {
public void run() {
TableModelEvent e = new TableModelEvent(UploadsTableModel.this);
for (Object aTableListener : listeners) {
TableModelListener listener = (TableModelListener) aTableListener;
listener.tableChanged(e);
}
}
};
UIUtil.invokeLaterInEDT(runner);
}
public void reverseList() {
sortAscending = !sortAscending;
synchronized (uploads) {
Collections.reverse(uploads);
}
fireModelChanged();
}
// Model helper methods ***************************************************
private void addOrUpdateUpload(Upload ul) {
if (isMetaFolderUpload(ul)) {
return;
}
boolean added = false;
int index;
synchronized (uploads) {
index = findUploadIndex(ul);
Upload alreadyUl = index >= 0 ? uploads.get(index) : null;
if (alreadyUl == null) {
uploads.add(ul);
added = true;
} else {
// Update if completely identical found
uploads.set(index, ul);
}
}
if (added) {
rowAdded();
} else {
rowsUpdated(index, index);
}
}
/**
* Searches downloads for a download with identical FileInfo.
*
* @param downloadArg
* download to search for identical copy
* @return index of the download with identical FileInfo, -1 if not found
*/
private int findUploadIndex(Upload uploadArg) {
for (int i = 0; i < uploads.size(); i++) {
Upload ul = uploads.get(i);
if (ul.getFile().isVersionDateAndSizeIdentical(uploadArg.getFile())
&& Util.equals(ul.getPartner(), uploadArg.getPartner()))
{
return i;
}
}
// No match
return -1;
}
/**
* Removes one upload from the model an returns its previous index
*
* @param upload
* @return the index where this upload was removed from.
*/
private int removeUpload(Upload upload) {
int index = uploads.indexOf(upload);
if (index >= 0) {
logFiner("Remove upload from tablemodel: " + upload);
uploads.remove(index);
} else {
logSevere("Unable to remove upload from tablemodel, not found: "
+ upload);
}
return index;
}
// TableModel interface ***************************************************
public int getColumnCount() {
return 6;
}
public int getRowCount() {
return uploads.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 COLTO :
return Translation.getTranslation("transfers.to");
}
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 UploadTableModel, not editable");
}
public void addTableModelListener(TableModelListener l) {
listeners.add(l);
}
public void removeTableModelListener(TableModelListener l) {
listeners.remove(l);
}
// Helper method **********************************************************
/**
* Tells listeners, that a new row at the end of the table has been added
*/
private void rowAdded() {
TableModelEvent e = new TableModelEvent(this, getRowCount() - 1,
getRowCount() - 1, TableModelEvent.ALL_COLUMNS,
TableModelEvent.INSERT);
modelChanged(e);
}
private synchronized 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,
TableModelEvent.ALL_COLUMNS, TableModelEvent.UPDATE);
modelChanged(e);
}
/**
* fire change on whole model
*/
private void rowsUpdatedAll() {
rowsUpdated(0, uploads.size());
}
/**
* Fires an modelevent to all listeners, that model has changed
*/
private void modelChanged(final TableModelEvent e) {
// logFiner("Upload tablemodel changed");
Runnable runner = new Runnable() {
public void run() {
synchronized (listeners) {
for (Object listener1 : listeners) {
TableModelListener listener = (TableModelListener) listener1;
listener.tableChanged(e);
}
}
}
};
if (EventQueue.isDispatchThread()) {
runner.run();
} else {
UIUtil.invokeLaterInEDT(runner);
}
}
public int getSortColumn() {
return sortColumn;
}
public boolean isSortAscending() {
return sortAscending;
}
public Class getColumnClass(int columnIndex) {
switch (columnIndex) {
case COLTYPE :
case COLFILE :
return FileInfo.class;
case COLPROGRESS :
return Upload.class;
case COLSIZE :
return Long.class;
case COLFOLDER :
return FolderInfo.class;
case COLTO :
return Member.class;
}
return null;
}
public Object getValueAt(int rowIndex, int columnIndex) {
if (rowIndex >= uploads.size()) {
logSevere("Illegal rowIndex requested. rowIndex " + rowIndex
+ ", uploads " + uploads.size());
return null;
}
Upload upload = uploads.get(rowIndex);
switch (columnIndex) {
case COLTYPE :
case COLFILE :
return upload.getFile();
case COLPROGRESS :
return upload;
case COLSIZE :
return upload.getFile().getSize();
case COLFOLDER :
return upload.getFile().getFolderInfo();
case COLTO :
return upload.getPartner();
}
return null;
}
public void setAscending(boolean ascending) {
sortAscending = ascending;
}
public Upload[] getUploadsAtRows(int[] ints) {
Upload[] rows = new Upload[ints.length];
int x = 0;
for (int i : ints) {
if (i < uploads.size()) {
rows[x] = uploads.get(i);
}
x++;
}
return rows;
}
public int getSelectedRowCount() {
return 0; // To change body of created methods use File | Settings |
// File Templates.
}
// ////////////////
// Inner Classes //
// ////////////////
/**
* Listener on Transfer manager with new event system. TODO: Consolidate
* removing uploads on abort/complete/broken
*
* @author <a href="mailto:totmacher@powerfolder.com">Christian Sprajc </a>
*/
private class UploadTransferManagerListener extends TransferManagerAdapter {
public void uploadRequested(TransferManagerEvent event) {
addOrUpdateUpload(event.getUpload());
}
public void uploadStarted(TransferManagerEvent event) {
addOrUpdateUpload(event.getUpload());
}
public void uploadAborted(TransferManagerEvent event) {
if (isMetaFolderUpload(event.getUpload())) {
return;
}
int index = removeUpload(event.getUpload());
if (index >= 0) {
rowRemoved(index);
}
}
public void uploadBroken(TransferManagerEvent event) {
if (isMetaFolderUpload(event.getUpload())) {
return;
}
int index = removeUpload(event.getUpload());
if (index >= 0) {
rowRemoved(index);
}
}
public void uploadCompleted(TransferManagerEvent event) {
if (isMetaFolderUpload(event.getUpload())) {
return;
}
int index = uploads.indexOf(event.getUpload());
if (index >= 0) {
rowsUpdated(index, index);
} else {
logSevere("Upload not found in model: " + event.getUpload());
rowsUpdatedAll();
}
}
public boolean fireInEventDispatchThread() {
return true;
}
public void completedUploadRemoved(TransferManagerEvent event) {
if (isMetaFolderUpload(event.getUpload())) {
return;
}
int index = removeUpload(event.getUpload());
if (index >= 0) {
rowRemoved(index);
}
}
}
/**
* Continuously updates the model
*/
private class MyTimerTask extends TimerTask {
public void run() {
if (!periodicUpdate) {
// Skip
return;
}
resortAndUpdate();
}
}
private void resortAndUpdate() {
Runnable wrapper = new Runnable() {
public void run() {
if (fileInfoComparatorType == TransferComparator.BY_PROGRESS) {
// Always sort on a PROGRESS change, so that the table
// reorders correctly.
sort();
}
rowsUpdatedAll();
}
};
SwingUtilities.invokeLater(wrapper);
}
}