blob: dd606113f3445039639ae91cf610aa62007a3240 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 Poznan Supercomputing and Networking Center
* 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
*
* Contributors:
* Jan Konczak (PSNC) - initial implementation
******************************************************************************/
package org.eclipse.ptp.rm.smoa.ui.actions;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.Vector;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileInfo;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.core.runtime.Status;
import org.eclipse.ptp.remote.core.PTPRemoteCorePlugin;
import org.eclipse.ptp.rm.smoa.core.rservices.SMOAFileStore;
import org.eclipse.ptp.rm.smoa.core.rservices.SMOARemoteServices;
import org.eclipse.ptp.rm.smoa.core.util.NotifyShell;
import org.eclipse.ptp.rm.smoa.ui.SMOAUIPlugin;
import org.eclipse.ptp.ui.PTPUIPlugin;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
public class SMOACustomSyncAction extends AbstractHandler {
private class FileFormatException extends IOException {
private static final long serialVersionUID = -1360379446136195743L;
public FileFormatException(String s) {
super(s);
}
}
/** Policy if and when the files should be overwritten */
enum OverwritePolicy {
Always, IfNewer, Never
}
private static final QualifiedName SMOA_SYNC_OVERWRITE = new QualifiedName(
SMOAUIPlugin.PLUGIN_ID, "smoa.sync.rm.files.overwrite"); //$NON-NLS-1$
private static final String SMOA_SYNC_FILELIST_NAME = ".smoa_sync_file_list"; //$NON-NLS-1$
SMOARemoteServices rservices = (SMOARemoteServices) PTPRemoteCorePlugin
.getDefault().getRemoteServices("org.eclipse.ptp.remote.SMOARemoteServices"); //$NON-NLS-1$
// GUI components
private Tree remoteTree;
private Tree localTree;
private Combo policy;
// Data
/** Remote file store (rendered in remoteTree) */
private IFileStore sfs;
/** Local file store (rendered in localTree) */
private IFileStore lfs;
private OverwritePolicy overwrite;
private IProject project;
int counter;
private TreeItem addToTree(Tree toRegenerate, TreeItem treeItem,
IFileStore newStore) {
TreeItem parent = null;
if (treeItem.getParentItem() != null) {
parent = addToTree(toRegenerate, treeItem.getParentItem(), null);
}
TreeItem newItem;
if (parent == null) {
newItem = findChild(toRegenerate, treeItem.getText());
if (newItem == null) {
newItem = new TreeItem(toRegenerate, SWT.NONE);
newItem.setText(treeItem.getText());
}
} else {
newItem = findChild(parent, treeItem.getText());
if (newItem == null) {
newItem = new TreeItem(parent, SWT.NONE);
newItem.setText(treeItem.getText());
}
}
if (newStore != null) {
newItem.setData(newStore);
}
return newItem;
}
/**
* Copies a single tree item to store, and updates toRegenerate tree
*/
private void copyTreeItem(TreeItem treeItem, IFileStore store,
Tree toRegenerate) throws CoreException {
final IFileStore newStore = store
.getFileStore(new Path(getRelPath(treeItem)));
final IFileInfo info = ((IFileStore) treeItem.getData()).fetchInfo();
if (info.isDirectory()) {
newStore.mkdir(0, null);
newStore.putInfo(info, 0, null);
} else {
switch (overwrite) {
case Always:
break;
case Never:
if (newStore.fetchInfo().exists()) {
return;
}
break;
case IfNewer:
final IFileInfo newInfo = newStore.fetchInfo();
if (newInfo.exists()
&& newInfo.getLastModified() > info.getLastModified()) {
return;
}
break;
default:
throw new CoreException(new Status(IStatus.ERROR,
PTPUIPlugin.PLUGIN_ID, Messages.SMOACustomSyncAction_IncorretOverwritePolicy));
}
((IFileStore) treeItem.getData()).copy(newStore, EFS.OVERWRITE,
null);
}
addToTree(toRegenerate, treeItem, newStore);
}
@Override
public void dispose() {
// saveTrees(project);
super.dispose();
}
/**
* Called when user triggers this action from a menu (Described in
* plugin.properties)
*/
public Object execute(ExecutionEvent execEvent) throws ExecutionException {
try {
project = SelectConnetionAndDestDir.getSelectedProject(execEvent);
final SelectConnetionAndDestDir dialog = new SelectConnetionAndDestDir(
project);
if (!dialog.open()) {
return null;
}
sfs = dialog.getRemoteFileStore();
if (sfs == null) {
throw new ExecutionException(Messages.SMOACustomSyncAction_ErrorAccessingRemoteFS);
}
final Shell topShell = Display.getCurrent().getActiveShell();
final Shell shell = new Shell(topShell, new Shell().getStyle()
| SWT.APPLICATION_MODAL);
shell.setText(Messages.SMOACustomSyncAction_WindowTitle);
final GridLayout gridLayout = new GridLayout(2, true);
shell.setLayout(gridLayout);
new Label(shell, SWT.NONE).setText(Messages.SMOACustomSyncAction_Local);
new Label(shell, SWT.NONE).setText(Messages.SMOACustomSyncAction_Remote);
localTree = new Tree(shell, SWT.CHECK | SWT.VIRTUAL | SWT.MULTI);
localTree.setLayoutData(new GridData(GridData.FILL_BOTH));
final Listener treeListener = new Listener() {
public void handleEvent(Event event) {
assert event.item instanceof TreeItem;
final TreeItem me = (TreeItem) event.item;
if (me.getChecked()) {
TreeItem parentItem = me.getParentItem();
while (parentItem != null) {
parentItem.setChecked(true);
parentItem = parentItem.getParentItem();
}
for (final TreeItem item : me.getItems()) {
item.setChecked(true);
final Event e = new Event();
e.item = item;
this.handleEvent(e);
}
} else {
for (final TreeItem item : me.getItems()) {
item.setChecked(false);
final Event e = new Event();
e.item = item;
this.handleEvent(e);
}
}
}
};
remoteTree = new Tree(shell, SWT.CHECK | SWT.VIRTUAL | SWT.MULTI);
remoteTree.setLayoutData(new GridData(GridData.FILL_BOTH));
final Button copyLocalToRemote = new Button(shell, SWT.PUSH
| SWT.BORDER);
copyLocalToRemote.setLayoutData(new GridData(
GridData.HORIZONTAL_ALIGN_END));
copyLocalToRemote.setText(Messages.SMOACustomSyncAction_FromLocal);
copyLocalToRemote.addSelectionListener(new SelectionListener() {
public void widgetDefaultSelected(SelectionEvent arg0) {
}
public void widgetSelected(SelectionEvent arg0) {
toRemote();
// saveTrees(project);
}
});
final Button copyRemoteToLoacal = new Button(shell, SWT.PUSH
| SWT.BORDER);
copyRemoteToLoacal.setLayoutData(new GridData(
GridData.HORIZONTAL_ALIGN_BEGINNING));
copyRemoteToLoacal.setText(Messages.SMOACustomSyncAction_FromRemote);
copyRemoteToLoacal.addSelectionListener(new SelectionListener() {
public void widgetDefaultSelected(SelectionEvent arg0) {
}
public void widgetSelected(SelectionEvent arg0) {
fromRemote();
// saveTrees(project);
}
});
final Composite options = new Composite(shell, SWT.NONE);
GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
gridData.horizontalSpan = 2;
options.setLayoutData(gridData);
options.setLayout(new GridLayout(2, false));
new Label(options, SWT.NONE).setText(Messages.SMOACustomSyncAction_FileOverwritePolicy);
policy = new Combo(options, SWT.BORDER | SWT.READ_ONLY);
for (final OverwritePolicy p : OverwritePolicy.values()) {
policy.add(p.name());
}
final String policyVal = project
.getPersistentProperty(SMOA_SYNC_OVERWRITE);
if (policyVal != null) {
policy.select(policy.indexOf(policyVal));
} else {
policy.select(policy.indexOf(OverwritePolicy.Always.name()));
}
assert OverwritePolicy.valueOf(policy.getText()) != null;
overwrite = OverwritePolicy.valueOf(policy.getText());
policy.addSelectionListener(new SelectionListener() {
public void widgetDefaultSelected(SelectionEvent arg0) {
}
public void widgetSelected(SelectionEvent arg0) {
try {
assert OverwritePolicy.valueOf(policy.getText()) != null;
overwrite = OverwritePolicy.valueOf(policy.getText());
project.setPersistentProperty(SMOA_SYNC_OVERWRITE,
policy.getText());
} catch (final CoreException e) {
final MessageBox mb = new MessageBox(localTree.getShell(),
SWT.ICON_ERROR | SWT.OK);
mb.setText(Messages.SMOACustomSyncAction_ErrorOverwritePolicyTitle);
mb.setMessage(e.getLocalizedMessage());
mb.open();
}
}
});
final Shell progress = new Shell(topShell);
final GridLayout layout = new GridLayout();
progress.setLayout(layout);
final Label msg = new Label(progress, SWT.NONE);
msg.setText(Messages.SMOACustomSyncAction_PleaswWait);
gridData = new GridData(GridData.FILL_BOTH);
gridData.minimumWidth = 300;
msg.setLayoutData(gridData);
progress.pack();
progress.open();
lfs = rootFromProject(localTree, project, msg);
rootFromFileStore(remoteTree, sfs, msg);
if (!progress.isDisposed()) {
progress.close();
}
restoreTrees(project);
localTree.addListener(SWT.Selection, treeListener);
remoteTree.addListener(SWT.Selection, treeListener);
shell.open();
shell.addListener(SWT.Close, new Listener() {
public void handleEvent(Event event) {
saveTrees(project);
}
});
return null;
} catch (final CoreException e) {
NotifyShell.open(Messages.SMOACustomSyncAction_ExceptionBySynchro, e.getLocalizedMessage());
throw new ExecutionException(e.getLocalizedMessage(), e);
}
}
private TreeItem findChild(Tree parent, String text) {
for (final TreeItem it : parent.getItems()) {
if (it.getText().equals(text)) {
return it;
}
}
return null;
}
private TreeItem findChild(TreeItem parent, String text) {
for (final TreeItem it : parent.getItems()) {
if (it.getText().equals(text)) {
return it;
}
}
return null;
}
/** Calls synchronise in local ← remote direction */
protected void fromRemote() {
synchronise(getCheckedItems(remoteTree), lfs, localTree);
}
/**
* Returns array with relative paths to the checked items
*/
private TreeItem[] getCheckedItems(Tree tree) {
final Vector<TreeItem> v = new Vector<TreeItem>();
for (final TreeItem it : tree.getItems()) {
v.addAll(getCheckedItems(it));
}
return v.toArray(new TreeItem[v.size()]);
}
/**
* Returns all checked items under this item
*/
private Vector<TreeItem> getCheckedItems(TreeItem treeItem) {
final Vector<TreeItem> v = new Vector<TreeItem>();
for (final TreeItem it : treeItem.getItems()) {
if (it.getChecked()) {
v.add(it);
}
v.addAll(getCheckedItems(it));
}
return v;
}
/** Recursively builds up relative path representing the tree item */
String getRelPath(TreeItem item) {
if (item.getParentItem() != null) {
return getRelPath(item.getParentItem()) + "/" + item.getText(); //$NON-NLS-1$
}
return "."; //$NON-NLS-1$
}
/**
* Recursively and eagerly fills the tree with store and it's children
*/
private void populate(TreeItem tree, IFileStore store, Label msg)
throws CoreException {
counter++;
final TreeItem treeItem = new TreeItem(tree, SWT.NONE);
treeItem.setText(store.getName());
treeItem.setData(store);
if (msg != null && !msg.isDisposed()) {
msg.setText(Messages.SMOACustomSyncAction_RegeneratingTree_FoundFiles + counter);
msg.redraw();
msg.getShell().redraw();
msg.getDisplay().update();
}
if (store instanceof SMOAFileStore || store.fetchInfo().isDirectory()) {
for (final IFileStore fs : store.childStores(0, null)) {
populate(treeItem, fs, msg);
}
}
}
/**
* Reads the state of trees from project and applies to the trees
*/
private void restoreTrees(IProject project) {
try {
final BufferedReader in = new BufferedReader(new FileReader(project
.getLocation().toString() + "/" + SMOA_SYNC_FILELIST_NAME)); //$NON-NLS-1$
if (!"[Local]".equals(in.readLine())) { //$NON-NLS-1$
throw new FileFormatException(
"First line does not contain " + "[Local]"); //$NON-NLS-1$ //$NON-NLS-2$
}
int c;
while ((c = in.read()) == '\t') {
final String line = in.readLine();
if (line == null) {
return;
}
selectFromPath(localTree, line);
}
if (c == -1) {
return;
}
if (c != '[') {
throw new FileFormatException("Illegal line start (" + c + ")"); //$NON-NLS-1$ //$NON-NLS-2$
}
final String rem = in.readLine();
if (!"Remote]".equals(rem)) { //$NON-NLS-1$
throw new FileFormatException("Invalid line [" + rem); //$NON-NLS-1$
}
while ((c = in.read()) == '\t') {
final String line = in.readLine();
if (line == null) {
return;
}
selectFromPath(remoteTree, line);
}
if (c == -1) {
return;
}
throw new FileFormatException("Illegal line start (" + c + ")"); //$NON-NLS-1$ //$NON-NLS-2$
} catch (final FileNotFoundException e) {
// first run
} catch (final IOException e) {
final MessageBox mb = new MessageBox(localTree.getShell(), SWT.ICON_ERROR
| SWT.OK);
mb.setText(Messages.SMOACustomSyncAction_ExceptionByReadingSettings);
mb.setMessage(e.getLocalizedMessage());
mb.open();
}
}
/**
* Starts filling the tree basing on the given store. Updates the message
* label with progress.
*/
private void rootFromFileStore(Tree tree, IFileStore store, Label msg)
throws CoreException {
counter = 0;
final TreeItem treeItem = new TreeItem(tree, SWT.NONE);
treeItem.setText(store.getName());
treeItem.setData(store);
for (final IFileStore fs : store.childStores(0, null)) {
populate(treeItem, fs, msg);
}
}
/**
* Starts filling the tree basing on the project location. Updates the
* message label with progress.
*/
private IFileStore rootFromProject(Tree tree, IProject project, Label msg)
throws CoreException {
final IFileStore store = EFS.getLocalFileSystem().getStore(
project.getLocation());
rootFromFileStore(tree, store, msg);
return store;
}
/**
* Records the state of trees in Project
*/
protected void saveTrees(IProject project) {
try {
final DataOutputStream out = new DataOutputStream(new FileOutputStream(
project.getLocation().toString() + "/" //$NON-NLS-1$
+ SMOA_SYNC_FILELIST_NAME));
out.writeBytes("[Local]\n"); //$NON-NLS-1$
for (final TreeItem item : getCheckedItems(localTree)) {
out.write('\t');
out.writeBytes(getRelPath(item));
out.write('\n');
}
out.writeBytes("[Remote]\n"); //$NON-NLS-1$
for (final TreeItem item : getCheckedItems(remoteTree)) {
out.write('\t');
out.writeBytes(getRelPath(item));
out.write('\n');
}
} catch (final IOException e) {
final MessageBox mb = new MessageBox(localTree.getShell(), SWT.ICON_ERROR
| SWT.OK);
mb.setText(Messages.SMOACustomSyncAction_ExceptionByWritingSettings);
mb.setMessage(e.getLocalizedMessage());
mb.open();
}
}
/**
* Looks for a TreeItem corresponding to given path in given tree and
* selects it.
*/
private void selectFromPath(Tree tree, String file) {
TreeItem root = tree.getItems()[0];
TreeItem next = root;
root.setChecked(true);
final String[] path = new Path(file).segments();
for (int i = 0; i < path.length; ++i) {
for (final TreeItem item : root.getItems()) {
if (item.getText().equals(path[i])) {
next = item;
}
}
if (next == root) {
return;
}
root = next;
}
next.setChecked(true);
}
/**
* Copies files (given as array of {@link TreeItem}s) to another tree, given
* as the tree and it's starting store
*
* @param items
* - what to copy
* @param store
* - where to copy
* @param toRegenerate
* - which tree to update
*/
protected void synchronise(TreeItem[] items, IFileStore store,
Tree toRegenerate) {
final Shell topShell = Display.getCurrent().getActiveShell();
final Shell shell = new Shell(topShell, new Shell().getStyle()
| SWT.APPLICATION_MODAL);
shell.setText(Messages.SMOACustomSyncAction_ProgressWindowTitle);
shell.setLayout(new GridLayout(2, false));
new Label(shell, SWT.NONE).setText(Messages.SMOACustomSyncAction_FilesTotal);
new Label(shell, SWT.NONE).setText(Integer.toString(items.length));
new Label(shell, SWT.NONE).setText(Messages.SMOACustomSyncAction_FilesCopied);
final Label done = new Label(shell, SWT.NONE);
done.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
new Label(shell, SWT.NONE).setText(Messages.SMOACustomSyncAction_CurrentFile);
final Label current = new Label(shell, SWT.NONE);
final GridData gd = new GridData(GridData.FILL_HORIZONTAL);
gd.minimumWidth = 500;
current.setLayoutData(gd);
int doneVal = -1;
shell.pack(true);
shell.open();
try {
for (final TreeItem treeItem : items) {
if (!shell.isDisposed()) {
done.setText(Integer.toString(++doneVal));
current.setText(getRelPath(treeItem));
shell.pack(true);
done.redraw();
current.redraw();
shell.redraw();
shell.getDisplay().update();
}
copyTreeItem(treeItem, store, toRegenerate);
}
if (!shell.isDisposed()) {
done.setText(Integer.toString(++doneVal));
current.setText(Messages.SMOACustomSyncAction_RegeneratingTree);
shell.pack(true);
done.redraw();
current.redraw();
shell.redraw();
shell.getDisplay().update();
}
} catch (final CoreException e) {
final MessageBox mb = new MessageBox(Display.getCurrent()
.getActiveShell(), SWT.ICON_ERROR | SWT.OK);
mb.setText(Messages.SMOACustomSyncAction_ErrorDialogTitle);
mb.setMessage(e.getMessage());
mb.open();
}
if (!shell.isDisposed()) {
shell.close();
}
}
/** Calls synchronise in local → remote direction */
protected void toRemote() {
synchronise(getCheckedItems(localTree), sfs, remoteTree);
}
}