/*
* Created on 17 juil. 2003
*
* Copyright (C) 2004, 2005, 2006 Aelitis SAS, All rights Reserved
*
* This program 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; either version 2 of the License.
*
* This program 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 ( see the LICENSE file ).
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* AELITIS, SAS au capital de 46,603.30 euros,
* 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
*/
package org.gudy.azureus2.ui.swt.views;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.*;
import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.config.ParameterListener;
import org.gudy.azureus2.core3.disk.DiskManager;
import org.gudy.azureus2.core3.disk.DiskManagerFileInfo;
import org.gudy.azureus2.core3.download.DownloadManager;
import org.gudy.azureus2.core3.download.DownloadManagerState;
import org.gudy.azureus2.core3.download.DownloadManagerStateEvent;
import org.gudy.azureus2.core3.download.DownloadManagerStateListener;
import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.core3.util.*;
import org.gudy.azureus2.plugins.ui.tables.TableManager;
import org.gudy.azureus2.ui.swt.MessageBoxWindow;
import org.gudy.azureus2.ui.swt.Messages;
import org.gudy.azureus2.ui.swt.SimpleTextEntryWindow;
import org.gudy.azureus2.ui.swt.Utils;
import org.gudy.azureus2.ui.swt.views.file.FileInfoView;
import org.gudy.azureus2.ui.swt.views.table.TableViewSWT;
import org.gudy.azureus2.ui.swt.views.table.TableViewSWTMenuFillListener;
import org.gudy.azureus2.ui.swt.views.table.impl.TableViewSWTImpl;
import org.gudy.azureus2.ui.swt.views.table.impl.TableViewTab;
import org.gudy.azureus2.ui.swt.views.tableitems.files.*;
import org.gudy.azureus2.ui.swt.views.utils.ManagerUtils;
import com.aelitis.azureus.core.AzureusCoreOperation;
import com.aelitis.azureus.core.AzureusCoreOperationTask;
import com.aelitis.azureus.ui.common.RememberedDecisionsManager;
import com.aelitis.azureus.ui.common.table.*;
/**
* @author Olivier
* @author TuxPaper
* 2004/Apr/23: extends TableView instead of IAbstractView
*/
public class FilesView
extends TableViewTab
implements TableDataSourceChangedListener, TableSelectionListener,
TableViewSWTMenuFillListener, TableRefreshListener, DownloadManagerStateListener,
TableLifeCycleListener
{
boolean refreshing = false;
private static final TableColumnCore[] basicItems = {
new NameItem(),
new PathItem(),
new SizeItem(),
new DoneItem(),
new PercentItem(),
new FirstPieceItem(),
new PieceCountItem(),
new RemainingPiecesItem(),
new ProgressGraphItem(),
new ModeItem(),
new PriorityItem(),
new StorageTypeItem(),
new FileExtensionItem(),
};
private DownloadManager manager = null;
public static boolean show_full_path;
static{
COConfigurationManager.addAndFireParameterListener(
"FilesView.show.full.path",
new ParameterListener()
{
public void
parameterChanged(
String parameterName)
{
show_full_path = COConfigurationManager.getBooleanParameter( "FilesView.show.full.path" );
}
});
}
private MenuItem path_item;
private TableViewSWT tv;
/**
* Initialize
*/
public FilesView() {
tv = new TableViewSWTImpl(TableManager.TABLE_TORRENT_FILES, "FilesView",
basicItems, "firstpiece", SWT.MULTI | SWT.FULL_SELECTION | SWT.VIRTUAL);
setTableView(tv);
tv.setRowDefaultIconSize(new Point(16, 16));
tv.setEnableTabViews(true);
tv.setCoreTabViews(new IView[] { new FileInfoView()
});
tv.addTableDataSourceChangedListener(this, true);
tv.addRefreshListener(this, true);
tv.addSelectionListener(this, false);
tv.addMenuFillListener(this);
tv.addLifeCycleListener(this);
}
// @see com.aelitis.azureus.ui.common.table.TableDataSourceChangedListener#tableDataSourceChanged(java.lang.Object)
public void tableDataSourceChanged(Object newDataSource) {
DownloadManager old_manager = manager;
if (newDataSource == null)
manager = null;
else if (newDataSource instanceof Object[])
manager = (DownloadManager)((Object[])newDataSource)[0];
else
manager = (DownloadManager)newDataSource;
if (old_manager != null) {
old_manager.getDownloadState().removeListener(this);
}
if (manager != null) {
manager.getDownloadState().addListener(this);
}
tv.removeAllTableRows();
}
// @see com.aelitis.azureus.ui.common.table.TableSelectionListener#deselected(com.aelitis.azureus.ui.common.table.TableRowCore[])
public void deselected(TableRowCore[] rows) {
}
// @see com.aelitis.azureus.ui.common.table.TableSelectionListener#focusChanged(com.aelitis.azureus.ui.common.table.TableRowCore)
public void focusChanged(TableRowCore focus) {
}
// @see com.aelitis.azureus.ui.common.table.TableSelectionListener#selected(com.aelitis.azureus.ui.common.table.TableRowCore[])
public void selected(TableRowCore[] rows) {
}
// @see com.aelitis.azureus.ui.common.table.TableSelectionListener#defaultSelected(com.aelitis.azureus.ui.common.table.TableRowCore[])
public void defaultSelected(TableRowCore[] rows) {
DiskManagerFileInfo fileInfo = (DiskManagerFileInfo) tv.getFirstSelectedDataSource();
if (fileInfo != null
&& fileInfo.getAccessMode() == DiskManagerFileInfo.READ)
Utils.launch(fileInfo.getFile(true).toString());
}
// @see org.gudy.azureus2.ui.swt.views.TableViewSWTMenuFillListener#fillMenu(org.eclipse.swt.widgets.Menu)
public void fillMenu(final Menu menu) {
Shell shell = menu.getShell();
Object[] infos = tv.getSelectedDataSources();
boolean hasSelection = (infos.length > 0);
final MenuItem itemOpen = new MenuItem(menu, SWT.PUSH);
Messages.setLanguageText(itemOpen, "FilesView.menu.open");
Utils.setMenuItemImage(itemOpen, "run");
// Invoke open on enter, double click
menu.setDefaultItem(itemOpen);
// Explore (Copied from MyTorrentsView)
final boolean use_open_containing_folder = COConfigurationManager.getBooleanParameter("MyTorrentsView.menu.show_parent_folder_enabled");
final MenuItem itemExplore = new MenuItem(menu, SWT.PUSH);
Messages.setLanguageText(itemExplore, "MyTorrentsView.menu." + (use_open_containing_folder ? "open_parent_folder" : "explore"));
itemExplore.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event event) {
Object[] dataSources = tv.getSelectedDataSources();
for (int i = dataSources.length - 1; i >= 0; i--) {
DiskManagerFileInfo info = (DiskManagerFileInfo)dataSources[i];
if (info != null) {
File this_file = info.getFile(true);
File parent_file = (use_open_containing_folder) ? this_file.getParentFile() : null;
ManagerUtils.open((parent_file == null) ? this_file : parent_file);
}
}
}
});
itemExplore.setEnabled(hasSelection);
MenuItem itemRenameOrRetarget = null, itemRename = null, itemRetarget = null;
itemRenameOrRetarget = new MenuItem(menu, SWT.PUSH);
Messages.setLanguageText(itemRenameOrRetarget, "FilesView.menu.rename");
itemRenameOrRetarget.setData("rename", Boolean.valueOf(true));
itemRenameOrRetarget.setData("retarget", Boolean.valueOf(true));
itemRename = new MenuItem(menu, SWT.PUSH);
itemRetarget = new MenuItem(menu, SWT.PUSH);
Messages.setLanguageText(itemRename, "FilesView.menu.rename_only");
Messages.setLanguageText(itemRetarget, "FilesView.menu.retarget");
itemRename.setData("rename", Boolean.valueOf(true));
itemRename.setData("retarget", Boolean.valueOf(false));
itemRetarget.setData("rename", Boolean.valueOf(false));
itemRetarget.setData("retarget", Boolean.valueOf(true));
final MenuItem itemPriority = new MenuItem(menu, SWT.CASCADE);
Messages.setLanguageText(itemPriority, "FilesView.menu.setpriority"); //$NON-NLS-1$
final Menu menuPriority = new Menu(shell, SWT.DROP_DOWN);
itemPriority.setMenu(menuPriority);
final MenuItem itemHigh = new MenuItem(menuPriority, SWT.CASCADE);
itemHigh.setData("Priority", new Integer(0));
Messages.setLanguageText(itemHigh, "FilesView.menu.setpriority.high"); //$NON-NLS-1$
final MenuItem itemLow = new MenuItem(menuPriority, SWT.CASCADE);
itemLow.setData("Priority", new Integer(1));
Messages.setLanguageText(itemLow, "FilesView.menu.setpriority.normal"); //$NON-NLS-1$
final MenuItem itemSkipped = new MenuItem(menuPriority, SWT.CASCADE);
itemSkipped.setData("Priority", new Integer(2));
Messages.setLanguageText(itemSkipped, "FilesView.menu.setpriority.skipped"); //$NON-NLS-1$
final MenuItem itemDelete = new MenuItem(menuPriority, SWT.CASCADE);
itemDelete.setData("Priority", new Integer(3));
Messages.setLanguageText(itemDelete, "wizard.multitracker.delete"); // lazy but we're near release
new MenuItem(menu, SWT.SEPARATOR);
if (!hasSelection) {
itemOpen.setEnabled(false);
itemPriority.setEnabled(false);
itemRenameOrRetarget.setEnabled(false);
itemRename.setEnabled(false);
itemRetarget.setEnabled(false);
return;
}
boolean open = true;
boolean all_compact = true;
boolean all_skipped = true;
boolean all_priority = true;
boolean all_not_priority = true;
DiskManagerFileInfo[] dmi_array = new DiskManagerFileInfo[infos.length];
System.arraycopy(infos, 0, dmi_array, 0, infos.length);
int[] storage_types = manager.getStorageType(dmi_array);
for (int i = 0; i < infos.length; i++) {
DiskManagerFileInfo file_info = (DiskManagerFileInfo) infos[i];
if (open && file_info.getAccessMode() != DiskManagerFileInfo.READ) {
open = false;
}
if (all_compact && storage_types[i] != DiskManagerFileInfo.ST_COMPACT) {
all_compact = false;
}
if (all_skipped || all_priority || all_not_priority) {
if ( file_info.isSkipped()){
all_priority = false;
all_not_priority = false;
}else{
all_skipped = false;
// Only do this check if we need to.
if (all_not_priority || all_priority) {
if (file_info.isPriority()){
all_not_priority = false;
}else{
all_priority = false;
}
}
}
}
}
// we can only open files if they are read-only
itemOpen.setEnabled(open);
// can't rename files for non-persistent downloads (e.g. shares) as these
// are managed "externally"
itemRenameOrRetarget.setEnabled(manager.isPersistent());
itemRename.setEnabled(manager.isPersistent());
itemRetarget.setEnabled(manager.isPersistent());
itemSkipped.setEnabled( !all_skipped );
itemHigh.setEnabled( !all_priority );
itemLow.setEnabled( !all_not_priority );
itemDelete.setEnabled( !all_compact );
itemOpen.addListener(SWT.Selection, new TableSelectedRowsListener(tv) {
public void run(TableRowCore row) {
DiskManagerFileInfo fileInfo = (DiskManagerFileInfo)row.getDataSource(true);
if (fileInfo.getAccessMode() == DiskManagerFileInfo.READ) {
Utils.launch(fileInfo.getFile(true).toString());
}
}
});
Listener rename_listener = new Listener() {
public void handleEvent(Event event) {
boolean rename_it = ((Boolean)event.widget.getData("rename")).booleanValue();
boolean retarget_it = ((Boolean)event.widget.getData("retarget")).booleanValue();
rename(tv.getSelectedRows(), rename_it, retarget_it);
}
};
itemRenameOrRetarget.addListener(SWT.Selection, rename_listener);
itemRename.addListener(SWT.Selection, rename_listener);
itemRetarget.addListener(SWT.Selection, rename_listener);
Listener priorityListener = new Listener() {
public void handleEvent(Event event) {
changePriority(((Integer) event.widget.getData("Priority")).intValue(),
tv.getSelectedRows());
}
};
itemHigh.addListener(SWT.Selection, priorityListener);
itemLow.addListener(SWT.Selection, priorityListener);
itemSkipped.addListener(SWT.Selection, priorityListener);
itemDelete.addListener(SWT.Selection, priorityListener);
}
private String askForRenameFilename(DiskManagerFileInfo fileInfo) {
SimpleTextEntryWindow dialog = new SimpleTextEntryWindow(Display.getDefault());
dialog.setTitle("FilesView.rename.filename.title");
dialog.setMessage("FilesView.rename.filename.text");
dialog.setPreenteredText(fileInfo.getFile(true).getName(), false); // false -> it's not "suggested", it's a previous value
dialog.allowEmptyInput(false);
dialog.prompt();
if (!dialog.hasSubmittedInput()) {return null;}
return dialog.getSubmittedInput();
}
private String askForRetargetedFilename(DiskManagerFileInfo fileInfo) {
FileDialog fDialog = new FileDialog(Utils.findAnyShell(), SWT.SYSTEM_MODAL | SWT.SAVE);
File existing_file = fileInfo.getFile(true);
fDialog.setFilterPath(existing_file.getParent());
fDialog.setFileName(existing_file.getName());
fDialog.setText(MessageText.getString("FilesView.rename.choose.path"));
return fDialog.open();
}
private String askForSaveDirectory(DiskManagerFileInfo fileInfo) {
DirectoryDialog dDialog = new DirectoryDialog(Utils.findAnyShell(), SWT.SYSTEM_MODAL | SWT.SAVE);
File current_dir = fileInfo.getFile(true).getParentFile();
dDialog.setFilterPath(current_dir.getPath());
dDialog.setText(MessageText.getString("FilesView.rename.choose.path.dir"));
return dDialog.open();
}
private boolean askCanOverwrite(File file) {
return MessageBoxWindow.open(
"FilesView.messagebox.rename.id",
SWT.OK | SWT.CANCEL,
SWT.OK, true,
Display.getDefault(),
MessageBoxWindow.ICON_WARNING,
MessageText.getString( "FilesView.rename.confirm.delete.title" ),
MessageText.getString( "FilesView.rename.confirm.delete.text", new String[]{ file.toString()})) == SWT.OK;
}
// same code is used in tableitems.files.NameItem
private void moveFile(final DiskManagerFileInfo fileInfo, final File target) {
// this behaviour should be put further down in the core but I'd rather not
// do so close to release :(
final boolean[] result = { false };
is_changing_links = true;
FileUtil.runAsTask(new AzureusCoreOperationTask() {
public void run(AzureusCoreOperation operation) {
result[0] = fileInfo.setLink(target);
}
}
);
is_changing_links = false;
if (!result[0]){
MessageBox mb = new MessageBox(Utils.findAnyShell(), SWT.ICON_ERROR | SWT.OK);
mb.setText(MessageText.getString("FilesView.rename.failed.title"));
mb.setMessage(MessageText.getString("FilesView.rename.failed.text"));
mb.open();
}
}
protected void rename(TableRowCore[] rows, boolean rename_it, boolean retarget_it) {
if (manager == null) {return;}
if (rows.length == 0) {return;}
String save_dir = null;
if (!rename_it && retarget_it) {
save_dir = askForSaveDirectory((DiskManagerFileInfo)rows[0].getDataSource(true));
if (save_dir == null) {return;}
}
boolean paused = false;
try {
for (int i=0; i<rows.length; i++) {
TableRowCore row = rows[i];
DiskManagerFileInfo fileInfo = (DiskManagerFileInfo)rows[i].getDataSource(true);
File existing_file = fileInfo.getFile(true);
File f_target = null;
if (rename_it && retarget_it) {
String s_target = askForRetargetedFilename(fileInfo);
if (s_target != null)
f_target = new File(s_target);
}
else if (rename_it) {
String s_target = askForRenameFilename(fileInfo);
if (s_target != null)
f_target = new File(existing_file.getParentFile(), s_target);
}
else {
// Parent directory has changed.
f_target = new File(save_dir, existing_file.getName());
}
// So are we doing a rename?
// If the user has decided against it - abort the op.
if (f_target == null) {return;}
if (!paused) {paused = manager.pause();}
if (f_target.exists()){
// Nothing to do.
if (f_target.equals(existing_file))
continue;
// A rewrite will occur, so we need to ask the user's permission.
else if (existing_file.exists() && !askCanOverwrite(existing_file))
continue;
// If we reach here, then it means we are doing a real move, but there is
// no existing file.
}
moveFile(fileInfo, f_target);
row.invalidate();
}
}
finally {
if (paused){manager.resume();}
}
}
private void
changePriority(
int type ,
TableRowCore[] rows )
{
if ( manager == null){
return;
}
boolean paused = false;
boolean has_tried_pausing = false;
try{
int this_paused;
for (int i=0;i<rows.length;i++){
DiskManagerFileInfo fileInfo = (DiskManagerFileInfo)rows[i].getDataSource(true);
if ( type == 0){
fileInfo.setPriority(true);
this_paused = setSkipped( fileInfo, false, false, !has_tried_pausing);
}else if ( type == 1 ){
fileInfo.setPriority(false);
this_paused = setSkipped( fileInfo, false, false, !has_tried_pausing);
}else if ( type == 2 ){
this_paused = setSkipped( fileInfo, true, false, !has_tried_pausing);
}else{
this_paused = setSkipped( fileInfo, true, true, !has_tried_pausing);
}
if (this_paused != 0) {has_tried_pausing = true;}
paused = paused || (this_paused == 1);
}
}finally{
if ( paused ){
manager.resume();
}
}
}
// Returns:
// -1: Tried to pause, but it didn't need pausing.
// 1: Tried to pause, and it was paused.
// 0: Didn't attempt to pause.
private int
setSkipped(
DiskManagerFileInfo info,
boolean skipped,
boolean delete_action,
boolean try_to_pause)
{
// if we're not managing the download then don't do anything other than
// change the file's priority
if ( !manager.isPersistent()){
info.setSkipped( skipped );
return 0;
}
File existing_file = info.getFile(true);
int existing_storage_type = info.getStorageType();
// we can't ever have storage = COMPACT and !skipped
int new_storage_type;
if ( existing_file.exists()){
if (!skipped ){
new_storage_type = DiskManagerFileInfo.ST_LINEAR;
}else{
boolean delete_file;
if ( delete_action ){
delete_file =
MessageBoxWindow.open(
"FilesView.messagebox.delete.id",
SWT.OK | SWT.CANCEL,
SWT.OK, true,
Display.getDefault(),
MessageBoxWindow.ICON_WARNING,
MessageText.getString( "FilesView.rename.confirm.delete.title" ),
MessageText.getString( "FilesView.rename.confirm.delete.text", new String[]{ existing_file.toString()})) == SWT.OK;
}else{
// OK, too many users have got confused over the option to truncate files when selecting
// do-not-download so I'm removing it
delete_file = false;
/*
delete_file =
MessageBoxWindow.open(
"FilesView.messagebox.skip.id",
SWT.YES | SWT.NO,
SWT.YES | SWT.NO,
getComposite().getDisplay(),
MessageBoxWindow.ICON_WARNING,
MessageText.getString( "FilesView.rename.confirm.delete.title" ),
MessageText.getString( "FilesView.skip.confirm.delete.text", new String[]{ existing_file.toString()})) == SWT.YES;
*/
}
if ( delete_file ){
new_storage_type = DiskManagerFileInfo.ST_COMPACT;
}else{
new_storage_type = DiskManagerFileInfo.ST_LINEAR;
}
}
}else{
if ( skipped ){
boolean compact_disabled = RememberedDecisionsManager.getRememberedDecision(
"FilesView.messagebox.skip.id", SWT.YES | SWT.NO) == SWT.NO;
if ( compact_disabled ){
new_storage_type = DiskManagerFileInfo.ST_LINEAR;
}else{
new_storage_type = DiskManagerFileInfo.ST_COMPACT;
}
}else{
new_storage_type = DiskManagerFileInfo.ST_LINEAR;
}
}
boolean ok;
int paused = 0; // Did we try.
if ( existing_storage_type != new_storage_type ){
if (try_to_pause && new_storage_type == DiskManagerFileInfo.ST_COMPACT ){
paused = (manager.pause()) ? 1 : -1;
}
ok = info.setStorageType( new_storage_type );
}else{
ok = true;
}
if ( ok ){
info.setSkipped( skipped );
}
return( paused );
}
// @see com.aelitis.azureus.ui.common.table.TableRefreshListener#tableRefresh()
private boolean force_refresh = false;
public void tableRefresh() {
if (refreshing)
return;
try {
refreshing = true;
if (tv.isDisposed())
return;
DiskManagerFileInfo files[] = getFileInfo();
if (files != null && (this.force_refresh || !doAllExist(files))) {
this.force_refresh = false;
Object[] datasources = tv.getDataSources();
if(datasources.length == files.length)
{
// check if we actually have to replace anything
ArrayList toAdd = new ArrayList(Arrays.asList(files));
ArrayList toRemove = new ArrayList();
for(int i = 0;i < datasources.length;i++)
{
DiskManagerFileInfo info = (DiskManagerFileInfo)datasources[i];
if(files[info.getIndex()] == info)
toAdd.set(info.getIndex(), null);
else
toRemove.add(info);
}
tv.removeDataSources(toRemove.toArray());
tv.addDataSources(toAdd.toArray());
((TableViewSWTImpl)tv).tableInvalidate();
} else
{
tv.removeAllTableRows();
Object filesCopy[] = new Object[files.length];
System.arraycopy(files, 0, filesCopy, 0, files.length);
tv.addDataSources(filesCopy);
}
tv.processDataSourceQueue();
}
} finally {
refreshing = false;
}
}
/**
* @param files
* @return
*
* @since 3.0.0.7
*/
private boolean doAllExist(DiskManagerFileInfo[] files) {
for (int i = 0; i < files.length; i++) {
DiskManagerFileInfo fileinfo = files[i];
// We can't just use tv.dataSourceExists(), since it does a .equals()
// comparison, and we want a reference comparison
TableRowCore row = tv.getRow(fileinfo);
if (row == null) {
return false;
}
// reference comparison
if (row.getDataSource(true) != fileinfo) {
return false;
}
}
return true;
}
/* SubMenu for column specific tasks.
*/
public void addThisColumnSubMenu(String sColumnName, Menu menuThisColumn) {
if (sColumnName.equals("path")) {
path_item = new MenuItem( menuThisColumn, SWT.CHECK );
menuThisColumn.addListener( SWT.Show, new Listener() {
public void handleEvent(Event e) {
path_item.setSelection( show_full_path );
}
});
Messages.setLanguageText(path_item, "FilesView.fullpath");
path_item.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event e) {
show_full_path = path_item.getSelection();
tv.columnInvalidate("path");
tv.refreshTable(false);
COConfigurationManager.setParameter( "FilesView.show.full.path", show_full_path );
}
});
}
}
private DiskManagerFileInfo[]
getFileInfo()
{
if (manager == null)
return null;
return( manager.getDiskManagerFileInfo());
}
// Used to notify us of when we need to refresh - normally for external changes to the
// file links.
private boolean is_changing_links = false;
public void stateChanged(DownloadManagerState state, DownloadManagerStateEvent event) {
if (is_changing_links) {return;}
if (!DownloadManagerState.AT_FILE_LINKS.equals(event.getData())) {return;}
if (event.getType() != DownloadManagerStateEvent.ET_ATTRIBUTE_WRITTEN) {return;}
this.force_refresh = true;
}
public void tableViewInitialized() {}
public void tableViewDestroyed() {
if (manager != null) {
manager.getDownloadState().removeListener(this);
}
}
}