/*****************************************************************************
* Copyright (c) 2006-2008 g-Eclipse Consortium
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Initial development of the original code was made for the
* g-Eclipse project founded by European Union
* project number: FP6-IST-034327 http://www.geclipse.eu/
*
* Contributors:
* Mathias Stuempert - initial API and implementation
*****************************************************************************/
package eu.geclipse.ui.internal.transfer;
import java.net.URI;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileInfo;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import eu.geclipse.core.filesystem.GEclipseURI;
import eu.geclipse.core.filesystem.TransferInformation;
import eu.geclipse.core.filesystem.TransferManager;
import eu.geclipse.core.model.IGridConnection;
import eu.geclipse.core.model.IGridConnectionElement;
import eu.geclipse.core.model.IGridContainer;
import eu.geclipse.core.model.IGridElement;
import eu.geclipse.core.reporting.IProblem;
import eu.geclipse.core.reporting.ProblemException;
import eu.geclipse.ui.dialogs.ProblemDialog;
import eu.geclipse.ui.internal.Activator;
/**
* Job for transferring elements from local to local, local to remote,
* remote to local and remote to remote. Elements may either by copied
* or moved. A move operation is an ordinary copy operation followed
* by a delete operation. Both files and directories are supported.
*/
public class GridElementTransferOperation
extends Job {
private enum OverwriteMode {
/**
*
*/
ASK,
/**
*
*/
OVERWRITE_ALL,
/**
*
*/
IGNORE_ALL
}
/**
* The target of the transfer operation.
*/
private IGridContainer globalTarget;
/**
* The elements to be transfered.
*/
private IGridElement[] elements;
/**
* TransferOperation
*/
private TransferInformation transferOperation;
/**
* Determines if this is a copy or a move operation.
*/
private boolean move;
private OverwriteMode overwriteMode = OverwriteMode.ASK;
/**
* Create a new transfer operation. This may either be a copy or a
* move operation.
*
* @param elements The elements that should be transfered.
* @param target The destination of the transfer.
* @param move If true this operation will move the elements to the
* target and will afterward delete the original files, otherwise
* the original files will not be deleted.
*/
public GridElementTransferOperation( final IGridElement[] elements,
final IGridContainer target,
final boolean move ) {
super( Messages.getString( "GridElementTransferOperation.transfer_name" ) ); //$NON-NLS-1$
this.globalTarget = target;
this.elements = elements;
this.move = move;
}
/**
* Create a new grid element transfer operation for file stores.
*
* @param transferOperation transfer operation with information about transfer
*/
public GridElementTransferOperation( final TransferInformation transferOperation ) {
super( Messages.getString( "GridElementTransferOperation.transfer_name" ) ); //$NON-NLS-1$
this.transferOperation = transferOperation;
}
/**
* Get the destination of this transfer.
*
* @return This transfer's target.
*/
public IGridContainer getTarget() {
return this.globalTarget;
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public IStatus run( final IProgressMonitor monitor ) {
IProgressMonitor localMonitor
= monitor == null
? new NullProgressMonitor()
: monitor;
MultiStatus status = new MultiStatus( Activator.PLUGIN_ID,
IStatus.OK,
Messages.getString("GridElementTransferOperation.op_status"), //$NON-NLS-1$
null );
if( this.transferOperation != null ) {
//Resume file stores
localMonitor.beginTask( Messages.getString("GridElementTransferOperation.transfering_element_progress"), 1 ); //$NON-NLS-1$
String sourceName = new Path( this.transferOperation.getSource()
.toURI()
.getPath() ).lastSegment();
TransferParams data = new TransferParams( this.transferOperation.getSource(),
null,
this.transferOperation.getDestination(),
this.transferOperation.getDestination()
.getChild( sourceName ),
localMonitor );
try {
checkExistingTarget( data );
} catch( ProblemException exc ) {
String msg = String.format( Messages.getString( "GridElementTransferOperation.errCannotCopyFile" ), //$NON-NLS-1$
sourceName );
data.status = new Status( IStatus.ERROR, Activator.PLUGIN_ID, msg, exc );
status.merge( data.status );
}
if ( localMonitor.isCanceled() ) {
throw new OperationCanceledException();
}
if( data.status.isOK() ) {
IStatus resStatus = TransferManager.getManager().resumeTransfer( this.transferOperation, localMonitor );
status.merge( resStatus );
}
} else {
//Transfer grid elements
localMonitor.beginTask( Messages.getString("GridElementTransferOperation.transfering_element_progress"), this.elements.length + 1 ); //$NON-NLS-1$
for ( int i = 0 ; i < this.elements.length ; i++ ) {
localMonitor.subTask( this.elements[i].getName() );
IProgressMonitor subMonitor = new SubProgressMonitor( localMonitor, 1 );
IStatus tempStatus = transferElement( this.elements[i], this.globalTarget, subMonitor );
if ( !tempStatus.isOK() ) {
status.merge( tempStatus );
}
if ( localMonitor.isCanceled() ) {
throw new OperationCanceledException();
}
if ( this.move ) {
IGridContainer parent = this.elements[i].getParent();
boolean refresh = true;
for ( int j = i+1 ; j < this.elements.length ; j++ ) {
if ( this.elements[j].getParent().equals( parent ) ) {
refresh = false;
break;
}
}
if ( refresh ) {
try {
// TODO mathias progress monitoring
parent.refresh( null );
} catch ( ProblemException pExc ) {
status.merge( pExc.getStatus() );
}
}
}
}
try {
if( status.isOK() ) {
if( this.globalTarget.getResource() instanceof IContainer ) {
//Refresh only if target is a container
this.globalTarget.refresh( new SubProgressMonitor( localMonitor, 1 ) );
} else {
this.globalTarget.getParent().refresh( new SubProgressMonitor( localMonitor, 1 ) );
for( IGridElement elem: this.globalTarget.getParent().getChildren( new SubProgressMonitor( localMonitor, 1 ) ) ) {
if( elem.getResource() instanceof IContainer && elem instanceof IGridContainer ) {
((IGridContainer)elem).refresh( new SubProgressMonitor( localMonitor,1 ) );
}
}
}
}
} catch ( ProblemException pExc ) {
status.merge( pExc.getStatus() );
}
}
if( status.getSeverity() == IStatus.ERROR ) {
showProblemDialog( status );
}
return Status.OK_STATUS; // always return OK, because we displays our own ProblemDialog
}
private void showProblemDialog( final MultiStatus status ) {
IWorkbench workbench = PlatformUI.getWorkbench();
IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
if( window == null ) {
IWorkbenchWindow[] windows = workbench.getWorkbenchWindows();
if( windows.length > 0 ) {
window = windows[ 0 ];
}
}
if( window != null ) {
final Shell shell = window.getShell();
shell.getDisplay().syncExec( new Runnable() {
public void run() {
ProblemDialog.openProblem( shell,
Messages.getString("GridElementTransferOperation.problemDialogTitle"), //$NON-NLS-1$
status.getChildren().length == 1 ? Messages.getString("GridElementTransferOperation.msgElementCannotBeCopied") : Messages.getString("GridElementTransferOperation.msgElementsCannotBeCopied"), //$NON-NLS-1$ //$NON-NLS-2$
createProblemException( status ) );
}
} );
}
}
ProblemException createProblemException( final MultiStatus multiStatus )
{
ProblemException problemException = null;
List<IStatus> failedStatuses = new ArrayList<IStatus>();
for( IStatus childStatus : multiStatus.getChildren() ) {
if( childStatus.getSeverity() != IStatus.OK ) {
failedStatuses.add( childStatus );
}
}
Throwable firstException = failedStatuses.size() > 0
? failedStatuses.get( 0 )
.getException()
: null;
problemException = new ProblemException( "eu.geclipse.ui.problem.tranfserOperationFailed", //$NON-NLS-1$
firstException,
Activator.PLUGIN_ID );
IProblem problem = problemException.getProblem();
for( IStatus status : failedStatuses ) {
String msg = status.getMessage();
if( status.getException() != null
&& !msg.equals( status.getException().getMessage() ) ) {
msg = msg + "\n" + status.getException().getMessage(); //$NON-NLS-1$
}
problem.addReason( msg );
}
return problemException;
}
/**
* Copy the specified file store to the destination file store.
* The source may be a file or a directory. Directories will be copied
* recursively.
*
* @param from The source of the transfer.
* @param to The target of the transfer.
* @param monitor A progress monitor used to track the transfer.
* @return A status object tracking errors of the transfer.
*/
private IStatus copy( final IFileStore from,
final IFileStore to,
final IProgressMonitor monitor ) {
TransferParams data = new TransferParams( from, null, to, to.getChild( from.getName() ), monitor );
try {
checkExistingTarget( data );
} catch( ProblemException exc ) {
String msg = String.format( Messages.getString( "GridElementTransferOperation.errCannotCopyFile" ), from.getName() ); //$NON-NLS-1$
data.status = new Status( IStatus.ERROR, Activator.PLUGIN_ID, msg, exc );
}
if ( monitor.isCanceled() ) {
throw new OperationCanceledException();
}
if( data.status.isOK() ) {
data.status = TransferManager.getManager().startTransfer( from,
to,
this.move,
monitor );
}
return data.status;
}
/**
* Private class holding parameters passed to methods during transferring file.
* Some parameters can be changed within method, and those changes should be visibled outside method,
* so its cannot be passed as normal parameters.
*/
private static class TransferParams {
IFileStore sourceFile;
IFileStore targetDirectory;
IFileStore targetFile;
IFileInfo sourceFileInfo;
IStatus status = Status.OK_STATUS;
IProgressMonitor monitor;
TransferParams( final IFileStore sourceFile, final IFileInfo sourceFileInfo, final IFileStore toDirectory, final IFileStore to, final IProgressMonitor monitor ) {
super();
this.sourceFile = sourceFile;
this.targetDirectory = toDirectory;
this.targetFile = to;
this.sourceFileInfo = sourceFileInfo;
this.monitor = monitor;
}
}
private IStatus transferElement( final IGridElement element,
final IGridContainer target,
final IProgressMonitor monitor ) {
IStatus status = Status.OK_STATUS;
monitor.beginTask( Messages.getString("GridElementTransferOperation.transfering_progress") + element.getName(), 10 ); //$NON-NLS-1$
if ( ! ( element instanceof IGridConnectionElement )
&& ! ( target instanceof IGridConnectionElement ) ) {
IResource sResource = element.getResource();
IResource tResource = target.getResource();
IPath destination = tResource.getFullPath().append( sResource.getName() );
try {
if ( this.move ) {
sResource.move( destination, IResource.NONE, monitor );
} else {
sResource.copy( destination, IResource.NONE, monitor );
}
} catch ( CoreException cExc ) {
status = new Status( IStatus.ERROR,
Activator.PLUGIN_ID,
IStatus.OK,
Messages.getString("GridElementTransferOperation.copy_resources_failed"), //$NON-NLS-1$
cExc );
}
}
else if ( ( element instanceof IGridConnection )
&& ! ( target instanceof IGridConnectionElement ) ) {
try {
IGridConnection connection = ( IGridConnection ) element;
URI uri = ( ( IGridConnection ) element ).getConnectionFileStore().toURI();
IResource sResource = element.getResource();
IContainer tFolder = ( IContainer ) target.getResource();
if ( connection.isFolder() ) {
IFolder folder = tFolder.getFolder( new Path( sResource.getName() ) );
folder.createLink( uri, IResource.NONE, new SubProgressMonitor( monitor, 5 ) );
} else {
IFile file = tFolder.getFile( new Path( sResource.getName() ) );
file.createLink( uri, IResource.NONE, new SubProgressMonitor( monitor, 5 ) );
}
if ( this.move ) {
sResource.delete( IResource.NONE, new SubProgressMonitor( monitor, 5 ) );
}
} catch ( CoreException cExc ) {
status = new Status( IStatus.ERROR,
Activator.PLUGIN_ID,
IStatus.OK,
Messages.getString("GridElementTransferOperation.copy_linked_resource_failed"), //$NON-NLS-1$
cExc );
}
}
else {
// Prepare variables
IFileStore inStore = null;
IFileStore outStore = null;
// Get input file store
try {
inStore = getFileStore( element );
} catch ( CoreException cExc ) {
status = new Status( IStatus.ERROR,
Activator.PLUGIN_ID,
IStatus.OK,
String.format( Messages.getString("GridElementTransferOperation.unable_get_filestore"), element.getName() ), //$NON-NLS-1$
cExc );
}
if ( status.isOK() ) {
try {
outStore = getFileStore( target );
} catch ( CoreException cExc ) {
status = new Status( IStatus.ERROR,
Activator.PLUGIN_ID,
IStatus.OK,
String.format( Messages.getString("GridElementTransferOperation.unable_get_filestore"), target.getName() ), //$NON-NLS-1$
cExc );
}
}
if ( status.isOK() ) {
// Put in try-finally-clause to ensure that monitor.done() is called
try {
// Copy operation
IProgressMonitor subMonitor = new SubProgressMonitor( monitor, this.move ? 9 : 10 );
if ( monitor.isCanceled() ) {
throw new OperationCanceledException();
}
status = copy( inStore, outStore, subMonitor );
/*
if ( status.isOK() ) {
startRefresh( target );
if ( monitor.isCanceled() ) {
throw new OperationCanceledException();
}
if ( this.move ) {
status = delete( inStore, new SubProgressMonitor( monitor, 3 ) );
if ( status.isOK() ) {
startRefresh( element.getParent() );
}
}
}
*/
} finally {
monitor.done();
}
}
}
return status;
}
/**
* Get the file store from the specified element.
*
* @param element The element to get the file store from.
* @return The element's file store.
* @throws CoreException If an error occurs while retrieving the file store.
*/
private IFileStore getFileStore( final IGridElement element )
throws CoreException {
IFileStore result = null;
if ( element instanceof IGridConnectionElement ) {
result = ( ( IGridConnectionElement ) element ).getConnectionFileStore();
} else {
result = element.getFileStore();
}
return result;
}
private void checkExistingTarget( final TransferParams data ) throws ProblemException {
IFileInfo targetInfo = null;
GEclipseURI geclURI = new GEclipseURI( data.targetFile.toURI() );
try {
targetInfo = EFS.getStore( geclURI.toSlaveURI() )
.fetchInfo( EFS.NONE, new NullProgressMonitor() );
} catch( ProblemException problemExc ) {
throw new ProblemException( "eu.geclipse.core.filesystem.serverCouldNotBeContacted",
String.format( "Could not contact server to fetch file info for %s",
data.targetFile.toURI().toString() ),
problemExc,
Activator.PLUGIN_ID );
} catch( CoreException exc ) {
throw new ProblemException( "eu.geclipse.core.problem.net.malformedURL",
String.format( "URL %s is malformed or EFS using scheme %s doesn't exist",
data.targetFile.toURI().toString(),
geclURI.getSlaveScheme() ),
exc,
Activator.PLUGIN_ID );
}
if( targetInfo != null && targetInfo.exists() ) {
if( data.sourceFile.toURI().equals( data.targetFile.toURI() ) ) {
String msg = String.format( Messages.getString("GridElementTransferOperation.errSourceAndTargetAreTheSame"), data.sourceFile.getName() ); //$NON-NLS-1$
throw new ProblemException( "eu.geclipse.ui.problem.fileCannotOverwriteItself", msg, Activator.PLUGIN_ID ); //$NON-NLS-1$
}
switch( this.overwriteMode ) {
case ASK:
askOverwrite( data, targetInfo );
break;
case OVERWRITE_ALL:
deleteTarget( data );
break;
case IGNORE_ALL:
ignoreTransfer( data );
break;
}
}
}
private void askOverwrite( final TransferParams data, final IFileInfo targetInfo ) {
class Runner implements Runnable {
int exitCode;
private final Display display;
Runner( final Display display ) {
this.display = display;
}
public void run() {
Shell shell = null;
String[] labels = { Messages.getString("GridElementTransferOperation.buttonYes"), Messages.getString("GridElementTransferOperation.buttonYesAll"), Messages.getString("GridElementTransferOperation.buttonNo"), Messages.getString("GridElementTransferOperation.buttonNoAll"), Messages.getString("GridElementTransferOperation.buttonCancel") }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
if( this.display != null ) {
shell = this.display.getActiveShell();
}
String message = String.format( Messages.getString("GridElementTransferOperation.overwriteMsg"), //$NON-NLS-1$
data.targetFile.getName(),
formatURI( data.targetFile.toURI() ),
formatDate( targetInfo.getLastModified() ),
formatURI( data.sourceFile.toURI() ),
"" //$NON-NLS-1$
);
MessageDialog dialog = new MessageDialog( shell,
Messages.getString("GridElementTransferOperation.overwriteTitle"), //$NON-NLS-1$
null,
message,
MessageDialog.QUESTION,
labels,
0 );
this.exitCode = dialog.open();
}
}
Display display = PlatformUI.getWorkbench().getDisplay();
Runner runner = new Runner( display );
display.syncExec( runner );
switch( runner.exitCode ) {
case 0:
deleteTarget( data );
break;
case 1:
this.overwriteMode = OverwriteMode.OVERWRITE_ALL;
deleteTarget( data );
break;
case 2:
ignoreTransfer( data );
break;
case 3:
this.overwriteMode = OverwriteMode.IGNORE_ALL;
ignoreTransfer( data );
break;
case 4:
data.status = Status.CANCEL_STATUS;
data.monitor.setCanceled( true );
break;
}
}
String formatURI( final URI uri ) {
String uriString = ""; //$NON-NLS-1$
GEclipseURI gUri = new GEclipseURI( uri );
URI slaveURI = gUri.toSlaveURI();
if( slaveURI == null ) {
uriString = uri.toString();
} else {
uriString = slaveURI.toString();
}
return uriString;
}
String formatDate( final long lastModified ) {
String dateString = ""; //$NON-NLS-1$
if( lastModified != EFS.NONE ) {
dateString = DateFormat.getDateTimeInstance().format( new Date( lastModified ) );
}
return dateString;
}
private void deleteTarget( final TransferParams data ) {
String filename = data.targetFile.getName();
try {
data.monitor.subTask( String.format( Messages.getString("GridElementTransferOperation.deletingTarget"), filename ) ); //$NON-NLS-1$
data.targetFile.delete( EFS.NONE, data.monitor );
if( !data.monitor.isCanceled() ) {
data.targetFile = data.targetDirectory.getChild( filename );
}
} catch( CoreException exception ) {
data.status = new Status( IStatus.ERROR, Activator.PLUGIN_ID, String.format( Messages.getString("GridElementTransferOperation.errCannotDeleteTarget"), data.targetFile.getName() ), exception ); //$NON-NLS-1$
}
}
private void ignoreTransfer( final TransferParams data ) {
data.status = new Status( IStatus.INFO, Activator.PLUGIN_ID, Messages.getString("GridElementTransferOperation.targetExists") ); //$NON-NLS-1$
}
/*
private void startRefresh( final IGridContainer container ) {
Job refreshJob = new Job( "Refresh @ " + container.getName() ) {
@Override
protected IStatus run( final IProgressMonitor monitor ) {
try {
container.refresh( new SubProgressMonitor( monitor, 1 ) );
} catch ( ProblemException pExc ) {
Activator.logException( pExc );
}
return Status.OK_STATUS;
}
};
refreshJob.setSystem( true );
refreshJob.schedule();
}
*/
}