Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal')
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/ImageConsts.java52
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/UserAccount.java95
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/adapters/NodeStateFilter.java50
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/adapters/NodeStateFilterFactory.java62
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/autosave/SaveAllListener.java194
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/autosave/SaveListener.java143
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/EditableSharedDocumentAdapter.java256
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/LocalFileSaveable.java479
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/LocalTypedElement.java306
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/MergeEditorInput.java409
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/MergeInput.java158
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/MergeTypedElement.java100
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/RemoteTypedElement.java104
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/events/INodeStateListener.java26
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/exceptions/TCFChannelException.java40
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/exceptions/TCFException.java40
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/exceptions/TCFFileSystemException.java41
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/explorer/FSExplorerEditorPage.java134
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/CacheManager.java467
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/CachePropertyTester.java37
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/CommitHandler.java90
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/ContentTypeHelper.java134
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/FSTreeNodePropertyTester.java59
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/MergeHandler.java46
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/OpenFileHandler.java163
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/OpenWithContribution.java77
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/OpenWithMenu.java464
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/PersistenceManager.java510
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/RefreshHandler.java45
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/RevertHandler.java38
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/StateManager.java300
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/TimeTriggeredProgressMonitorDialog.java240
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/UpdateHandler.java89
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/UserManager.java207
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/help/IContextHelpIds.java29
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/nls/Messages.java221
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/nls/Messages.properties153
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/preferences/PreferencesInitializer.java46
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/preferences/TargetExplorerPreferencePage.java61
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/properties/AdvancedAttributesDialog.java225
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/properties/GeneralInformationPage.java404
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/url/Rendezvous.java107
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/url/TcfInputStream.java172
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/url/TcfOutputStream.java146
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/url/TcfURLConnection.java325
45 files changed, 7544 insertions, 0 deletions
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/ImageConsts.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/ImageConsts.java
new file mode 100644
index 000000000..d51e06701
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/ImageConsts.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal;
+
+/**
+ * File system plug-in Image registry constants.
+ */
+public interface ImageConsts {
+
+ // ***** The directory structure constants *****
+
+ /**
+ * The root directory where to load the images from, relative to
+ * the bundle directory.
+ */
+ public final static String IMAGE_DIR_ROOT = "icons/"; //$NON-NLS-1$
+
+ /**
+ * The directory where to load model object images from,
+ * relative to the image root directory.
+ */
+ public final static String IMAGE_DIR_OBJ = "obj16/"; //$NON-NLS-1$
+
+ // ***** The image constants *****
+
+ /**
+ * The key to access the base folder object image.
+ */
+ public static final String FOLDER = "Folder"; //$NON-NLS-1$
+
+ /**
+ * The key to access the base folder object image.
+ */
+ public static final String ROOT_DRIVE = "RootDrive"; //$NON-NLS-1$
+
+ /**
+ * The key to access the base folder object image.
+ */
+ public static final String ROOT_DRIVE_OPEN = "RootDriveOpen"; //$NON-NLS-1$
+
+ /**
+ * The key to access the image of compare editor.
+ */
+ public static final String COMPARE_EDITOR = "CompareEditor"; //$NON-NLS-1$
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/UserAccount.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/UserAccount.java
new file mode 100644
index 000000000..f4fc867ec
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/UserAccount.java
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ * William Chen (Wind River) - [352302]Opening a file in an editor depending on
+ * the client's permissions.
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal;
+
+/**
+ * The data model of a user account.
+ */
+public class UserAccount {
+ // The user's id.
+ private int uid;
+ // The user's group id.
+ private int gid;
+ // The user's effective id.
+ private int euid;
+ // The user's effective group id.
+ private int egid;
+ // The user's home directory.
+ private String home;
+
+ /**
+ * Create a user account with given data.
+ *
+ * @param uid
+ * The user's id
+ * @param gid
+ * The user's group id
+ * @param euid
+ * The user's effective id.
+ * @param egid
+ * The user's effective group id.
+ * @param home
+ * The user's home directory.
+ */
+ public UserAccount(int uid, int gid, int euid, int egid, String home) {
+ this.uid = uid;
+ this.gid = gid;
+ this.euid = euid;
+ this.egid = egid;
+ this.home = home;
+ }
+
+ /**
+ * Get the user's id.
+ *
+ * @return The user's id.
+ */
+ public int getUID() {
+ return uid;
+ }
+
+ /**
+ * Get the user's group id.
+ *
+ * @return The user's group id.
+ */
+ public int getGID() {
+ return gid;
+ }
+
+ /**
+ * Get the user's effective id.
+ *
+ * @return The user's effective id.
+ */
+ public int getEUID() {
+ return euid;
+ }
+
+ /**
+ * Get the user's effective group id.
+ *
+ * @return The user's effective group id.
+ */
+ public int getEGID() {
+ return egid;
+ }
+
+ /**
+ * Get the user's home directory.
+ *
+ * @return The user's home directory.
+ */
+ public String getHome() {
+ return home;
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/adapters/NodeStateFilter.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/adapters/NodeStateFilter.java
new file mode 100644
index 000000000..bd9c75bd5
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/adapters/NodeStateFilter.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ * William Chen (Wind River)- [345552] Edit the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.adapters;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.tcf.te.tcf.filesystem.internal.handlers.StateManager;
+import org.eclipse.tcf.te.tcf.filesystem.model.CacheState;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSTreeNode;
+import org.eclipse.ui.IActionFilter;
+
+/**
+ * This action filter wraps an FSTreeNode and test its attribute of "cache.state".
+ * It serves as the expression filter of decorations of Target Explorer.
+ */
+public class NodeStateFilter implements IActionFilter {
+ private FSTreeNode node;
+
+ /**
+ * Constructor.
+ *
+ * @param node
+ * The wrapped tree node. Must not be <code>null</code>.
+ */
+ public NodeStateFilter(FSTreeNode node) {
+ Assert.isNotNull(node);
+ this.node = node;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.IActionFilter#testAttribute(java.lang.Object, java.lang.String, java.lang.String)
+ */
+ @Override
+ public boolean testAttribute(Object target, String name, String value) {
+ if (name.equals("cache.state") && node.isFile()) { //$NON-NLS-1$
+ CacheState state = StateManager.getInstance().getCacheState(node);
+ if (value == null)
+ value = CacheState.consistent.name();
+ return value.equals(state.name());
+ }
+ return false;
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/adapters/NodeStateFilterFactory.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/adapters/NodeStateFilterFactory.java
new file mode 100644
index 000000000..1d66b04e0
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/adapters/NodeStateFilterFactory.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ * William Chen (Wind River)- [345552] Edit the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.adapters;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.core.runtime.IAdapterFactory;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSTreeNode;
+import org.eclipse.ui.IActionFilter;
+
+/**
+ * The adapter factory of <code>FSTreeNode</code> over <code>IActionFilter</code>
+ */
+@SuppressWarnings("rawtypes")
+public class NodeStateFilterFactory implements IAdapterFactory {
+ // The ADAPTERS adapted by this factory.
+ private static Class[] ADAPTERS = {IActionFilter.class};
+ // The fFilters map caching fFilters for FS nodes.
+ private Map<FSTreeNode, NodeStateFilter> filters;
+
+ /**
+ * Constructor.
+ */
+ public NodeStateFilterFactory(){
+ this.filters = Collections.synchronizedMap(new HashMap<FSTreeNode, NodeStateFilter>());
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.runtime.IAdapterFactory#getAdapter(java.lang.Object, java.lang.Class)
+ */
+ @Override
+ public Object getAdapter(Object adaptableObject, Class adapterType) {
+ if(adaptableObject instanceof FSTreeNode){
+ FSTreeNode node = (FSTreeNode) adaptableObject;
+ NodeStateFilter filter = filters.get(node);
+ if(filter == null){
+ filter = new NodeStateFilter(node);
+ filters.put(node, filter);
+ }
+ return filter;
+ }
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.runtime.IAdapterFactory#getAdapterList()
+ */
+ @Override
+ public Class[] getAdapterList() {
+ return ADAPTERS;
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/autosave/SaveAllListener.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/autosave/SaveAllListener.java
new file mode 100644
index 000000000..26538d35f
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/autosave/SaveAllListener.java
@@ -0,0 +1,194 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * William Chen (Wind River) - [345552] Edit the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.autosave;
+
+import java.io.File;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.compare.CompareUI;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.core.commands.IExecutionListener;
+import org.eclipse.core.commands.NotHandledException;
+import org.eclipse.core.filesystem.EFS;
+import org.eclipse.core.filesystem.IFileStore;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.tcf.te.tcf.filesystem.internal.compare.LocalTypedElement;
+import org.eclipse.tcf.te.tcf.filesystem.internal.compare.MergeEditorInput;
+import org.eclipse.tcf.te.tcf.filesystem.internal.compare.RemoteTypedElement;
+import org.eclipse.tcf.te.tcf.filesystem.internal.exceptions.TCFException;
+import org.eclipse.tcf.te.tcf.filesystem.internal.handlers.CacheManager;
+import org.eclipse.tcf.te.tcf.filesystem.internal.handlers.PersistenceManager;
+import org.eclipse.tcf.te.tcf.filesystem.internal.handlers.StateManager;
+import org.eclipse.tcf.te.tcf.filesystem.internal.nls.Messages;
+import org.eclipse.tcf.te.tcf.filesystem.model.CacheState;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSModel;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSTreeNode;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IURIEditorInput;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+/**
+ * The execution listener of command "SAVE ALL", which synchronizes the local
+ * file with the one on the target server after it is saved.
+ */
+public class SaveAllListener implements IExecutionListener {
+ // Dirty nodes that should be saved and synchronized.
+ private List<FSTreeNode> fDirtyNodes;
+ // The file system fModel storing the existing FSTreeNodes.
+ private FSModel fModel;
+ /**
+ * Create the listener listening to command "SAVE ALL".
+ */
+ public SaveAllListener() {
+ this.fModel = FSModel.getInstance();
+ this.fDirtyNodes = new ArrayList<FSTreeNode>();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.commands.IExecutionListener#postExecuteSuccess(java.lang.String, java.lang.Object)
+ */
+ @Override
+ public void postExecuteSuccess(String commandId, Object returnValue) {
+ if (!fDirtyNodes.isEmpty()) {
+ try {
+ List<FSTreeNode> modified = new ArrayList<FSTreeNode>();
+ List<FSTreeNode> conflicts = new ArrayList<FSTreeNode>();
+ for (FSTreeNode node : fDirtyNodes) {
+ // Refresh the dirty nodes and get their latest states.
+ StateManager.getInstance().refreshState(node);
+ CacheState state = StateManager.getInstance().getCacheState(node);
+ switch (state) {
+ case consistent:
+ break;
+ case outdated:
+ break;
+ case modified:
+ // Reclassifying
+ modified.add(node);
+ break;
+ case conflict:
+ // Reclassifying
+ conflicts.add(node);
+ break;
+ }
+ }
+
+ if (PersistenceManager.getInstance().isAutoSaving()) {
+ // If auto saving is on.
+ if (!modified.isEmpty()) {
+ // Upload the modified nodes.
+ CacheManager.getInstance().upload(modified.toArray(new FSTreeNode[modified.size()]));
+ }
+ if (!conflicts.isEmpty()) {
+ // Merge the conflicting ones.
+ mergeConflicts(conflicts);
+ }
+ }
+ } catch (TCFException tcfe) {
+ Shell parent = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
+ MessageDialog.openError(parent, Messages.StateManager_RefreshFailureTitle, tcfe.getLocalizedMessage());
+ }
+ }
+ }
+
+ /**
+ * Merge those conflicting nodes.
+ *
+ * @param conflicts The conflicting nodes.
+ */
+ private void mergeConflicts(List<FSTreeNode> conflicts) {
+ for (FSTreeNode node : conflicts) {
+ String title = Messages.SaveAllListener_StateChangedDialogTitle;
+ String message = NLS.bind(Messages.SaveAllListener_SingularMessage, node.name);
+ IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
+ IWorkbenchPage page = window.getActivePage();
+ Shell parent = window.getShell();
+ MessageDialog msgDialog = new MessageDialog(parent, title, null, message,
+ MessageDialog.QUESTION, new String[] { Messages.SaveAllListener_Merge,
+ Messages.SaveAllListener_SaveAnyway, Messages.SaveAllListener_Cancel }, 0);
+ int index = msgDialog.open();
+ if (index == 0) { // Merge
+ LocalTypedElement local = new LocalTypedElement(node);
+ RemoteTypedElement remote = new RemoteTypedElement(node);
+ MergeEditorInput mergeInput = new MergeEditorInput(local, remote, page);
+ CompareUI.openCompareDialog(mergeInput);
+ } else if (index == 1) { // Save anyway
+ CacheManager.getInstance().upload(conflicts.toArray(new FSTreeNode[conflicts.size()]));
+ }
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.commands.IExecutionListener#preExecute(java.lang.String, org.eclipse.core.commands.ExecutionEvent)
+ */
+ @Override
+ public void preExecute(String commandId, ExecutionEvent event) {
+ fDirtyNodes.clear();
+ IWorkbenchPage page = HandlerUtil.getActiveWorkbenchWindow(event).getActivePage();
+ IEditorPart[] editors = page.getDirtyEditors();
+ for (IEditorPart editor : editors) {
+ IEditorInput input = editor.getEditorInput();
+ FSTreeNode node = getEditedNode(input);
+ if (node != null) {
+ // If it is a modified node, add it to the dirty node list.
+ fDirtyNodes.add(node);
+ }
+ }
+ }
+
+ /**
+ * Get the corresponding FSTreeNode from the input.
+ * If the input has no corresponding FSTreeNode, return null;
+ * @param input The editor input.
+ * @return The corresponding FSTreeNode or null if it has not.
+ */
+ private FSTreeNode getEditedNode(IEditorInput input){
+ if (input instanceof IURIEditorInput) {
+ //Get the file that is being edited.
+ IURIEditorInput fileInput = (IURIEditorInput) input;
+ URI uri = fileInput.getURI();
+ try {
+ IFileStore store = EFS.getStore(uri);
+ File localFile = store.toLocalFile(0, new NullProgressMonitor());
+ if (localFile != null) {
+ // Get the file's mapped FSTreeNode.
+ FSTreeNode node = fModel.getTreeNode(localFile.toString());
+ return node;
+ }
+ }catch(CoreException e){}
+ }
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.commands.IExecutionListener#notHandled(java.lang.String, org.eclipse.core.commands.NotHandledException)
+ */
+ @Override
+ public void notHandled(String commandId, NotHandledException exception) {
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.commands.IExecutionListener#postExecuteFailure(java.lang.String, org.eclipse.core.commands.ExecutionException)
+ */
+ @Override
+ public void postExecuteFailure(String commandId, ExecutionException exception) {
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/autosave/SaveListener.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/autosave/SaveListener.java
new file mode 100644
index 000000000..1aa6c083b
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/autosave/SaveListener.java
@@ -0,0 +1,143 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * William Chen (Wind River) - [345552] Edit the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.autosave;
+
+import java.io.File;
+import java.net.URI;
+
+import org.eclipse.compare.CompareUI;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.core.commands.IExecutionListener;
+import org.eclipse.core.commands.NotHandledException;
+import org.eclipse.core.filesystem.EFS;
+import org.eclipse.core.filesystem.IFileStore;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.tcf.te.tcf.filesystem.internal.compare.LocalTypedElement;
+import org.eclipse.tcf.te.tcf.filesystem.internal.compare.MergeEditorInput;
+import org.eclipse.tcf.te.tcf.filesystem.internal.compare.RemoteTypedElement;
+import org.eclipse.tcf.te.tcf.filesystem.internal.exceptions.TCFException;
+import org.eclipse.tcf.te.tcf.filesystem.internal.handlers.CacheManager;
+import org.eclipse.tcf.te.tcf.filesystem.internal.handlers.PersistenceManager;
+import org.eclipse.tcf.te.tcf.filesystem.internal.handlers.StateManager;
+import org.eclipse.tcf.te.tcf.filesystem.internal.nls.Messages;
+import org.eclipse.tcf.te.tcf.filesystem.model.CacheState;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSModel;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSTreeNode;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IURIEditorInput;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+/**
+ * The execution listener of command "SAVE", which synchronizes the local file
+ * with the one on the target server after it is saved.
+ */
+public class SaveListener implements IExecutionListener {
+ // Dirty node that should be committed or merged.
+ private FSTreeNode dirtyNode;
+ // The file system fModel.
+ private FSModel model;
+
+ /**
+ * Create a SaveListener listening to command "SAVE".
+ */
+ public SaveListener() {
+ this.model = FSModel.getInstance();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.commands.IExecutionListener#postExecuteSuccess(java.lang.String, java.lang.Object)
+ */
+ @Override
+ public void postExecuteSuccess(String commandId, Object returnValue) {
+ if (dirtyNode != null) {
+ try{
+ // Refresh the fDirtyNode's state.
+ StateManager.getInstance().refreshState(dirtyNode);
+ if (PersistenceManager.getInstance().isAutoSaving()) {
+ CacheState state = StateManager.getInstance().getCacheState(dirtyNode);
+ switch (state) {
+ case conflict:
+ String title = Messages.SaveListener_StateChangedDialogTitle;
+ String message = NLS.bind(Messages.SaveListener_StateChangedMessage, dirtyNode.name);
+ IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
+ IWorkbenchPage page = window.getActivePage();
+ Shell parent = window.getShell();
+ MessageDialog msgDialog = new MessageDialog(parent, title, null, message, MessageDialog.QUESTION,
+ new String[] { Messages.SaveListener_Merge, Messages.SaveListener_SaveAnyway, Messages.SaveListener_Cancel }, 0);
+ int index = msgDialog.open();
+ if (index == 0) {// Merge
+ LocalTypedElement local = new LocalTypedElement(dirtyNode);
+ RemoteTypedElement remote = new RemoteTypedElement(dirtyNode);
+ MergeEditorInput mergeInput = new MergeEditorInput(local, remote, page);
+ CompareUI.openCompareDialog(mergeInput);
+ } else if (index == 1) {// Save anyway.
+ CacheManager.getInstance().upload(dirtyNode);
+ }
+ break;
+ case modified:
+ // Save anyway
+ CacheManager.getInstance().upload(dirtyNode);
+ break;
+ case consistent:
+ break;
+ case outdated:
+ break;
+ }
+ }
+ }catch(TCFException tcfe){
+ Shell parent = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
+ MessageDialog.openError(parent, Messages.StateManager_RefreshFailureTitle, tcfe.getLocalizedMessage());
+ }
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.commands.IExecutionListener#preExecute(java.lang.String, org.eclipse.core.commands.ExecutionEvent)
+ */
+ @Override
+ public void preExecute(String commandId, ExecutionEvent event) {
+ dirtyNode = null;
+ IEditorInput input = HandlerUtil.getActiveEditorInput(event);
+ if (input instanceof IURIEditorInput) {
+ IURIEditorInput fileInput = (IURIEditorInput) input;
+ URI uri = fileInput.getURI();
+ try {
+ IFileStore store = EFS.getStore(uri);
+ File localFile = store.toLocalFile(0, new NullProgressMonitor());
+ if (localFile != null) {
+ dirtyNode = model.getTreeNode(localFile.toString());
+ }
+ }catch(CoreException e){
+ }
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.commands.IExecutionListener#notHandled(java.lang.String, org.eclipse.core.commands.NotHandledException)
+ */
+ @Override
+ public void notHandled(String commandId, NotHandledException exception) {
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.commands.IExecutionListener#postExecuteFailure(java.lang.String, org.eclipse.core.commands.ExecutionException)
+ */
+ @Override
+ public void postExecuteFailure(String commandId, ExecutionException exception) {
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/EditableSharedDocumentAdapter.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/EditableSharedDocumentAdapter.java
new file mode 100644
index 000000000..e6bd99e3f
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/EditableSharedDocumentAdapter.java
@@ -0,0 +1,256 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2008 IBM Corporation and others.
+ * 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:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.compare;
+
+import org.eclipse.compare.SharedDocumentAdapter;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.texteditor.IDocumentProvider;
+import org.eclipse.ui.texteditor.IElementStateListener;
+
+/**
+ * A shared document adapter that tracks whether the element is connected to a
+ * shared document and whether the contents have been flushed from a compare
+ * viewer. When contents are flushed, this adapter will connect to the document
+ * provider to ensure that the changes are not lost (see
+ * {@link #hasBufferedContents()}). In order to avoid a leak, the buffer must
+ * either be saved (see
+ * {@link #saveDocument(IEditorInput, boolean, IProgressMonitor)}) or released
+ * (see {@link #releaseBuffer()}).
+ * <p>
+ * This adapter must have a one-to-one correspondence to a typed element.
+ *
+ * @since 3.7 - Copied from
+ * org.eclipse.team.internal.ui.synchronize.EditableSharedDocumentAdapter
+ */
+public class EditableSharedDocumentAdapter extends SharedDocumentAdapter implements IElementStateListener {
+
+ private int connectionCount;
+ private final ISharedDocumentAdapterListener listener;
+ private IEditorInput bufferedKey;
+
+ /**
+ * Interface that provides this adapter with the state of the typed element
+ * and supports call backs to the element when the adapter state changes.
+ */
+ public interface ISharedDocumentAdapterListener {
+
+ /**
+ * Method that is invoked when the adapter connects to the document
+ * provider. This method is only invoked when the adapter first connects
+ * to the document.
+ */
+ void handleDocumentConnected();
+
+ /**
+ * Method that is invoked when the adapter disconnects from the document
+ * provider. This method is only invoked when the adapter no longer has
+ * any connection to the document provider.
+ */
+ void handleDocumentDisconnected();
+
+ /**
+ * Method invoked when changes in the document are flushed to the
+ * adapter.
+ */
+ void handleDocumentFlushed();
+
+ /**
+ * Method invoked when the file behind the shared document is deleted.
+ */
+ void handleDocumentDeleted();
+
+ /**
+ * Method invoked when the document dirty state changes from dirty to
+ * clean.
+ */
+ void handleDocumentSaved();
+ }
+
+ /**
+ * Create the shared document adapter for the given element.
+ *
+ * @param listener
+ * access to element internals
+ */
+ public EditableSharedDocumentAdapter(ISharedDocumentAdapterListener listener) {
+ super();
+ this.listener = listener;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.SharedDocumentAdapter#connect(org.eclipse.ui.texteditor.IDocumentProvider, org.eclipse.ui.IEditorInput)
+ */
+ @Override
+ public void connect(IDocumentProvider provider, IEditorInput documentKey)
+ throws CoreException {
+ super.connect(provider, documentKey);
+ connectionCount++;
+ if (connectionCount == 1) {
+ provider.addElementStateListener(this);
+ listener.handleDocumentConnected();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.SharedDocumentAdapter#disconnect(org.eclipse.ui.texteditor.IDocumentProvider, org.eclipse.ui.IEditorInput)
+ */
+ @Override
+ public void disconnect(IDocumentProvider provider, IEditorInput documentKey) {
+ try {
+ super.disconnect(provider, documentKey);
+ } finally {
+ if (connectionCount > 0)
+ connectionCount--;
+ if (connectionCount == 0) {
+ provider.removeElementStateListener(this);
+ listener.handleDocumentDisconnected();
+ }
+ }
+ }
+
+ /**
+ * Return whether the element is connected to a shared document.
+ *
+ * @return whether the element is connected to a shared document
+ */
+ public boolean isConnected() {
+ return connectionCount > 0;
+ }
+
+ /**
+ * Save the shared document of the element of this adapter.
+ *
+ * @param input
+ * the document key of the element.
+ * @param monitor
+ * a progress monitor
+ * @return whether the save succeeded or not
+ * @throws CoreException
+ */
+ public boolean saveDocument(IEditorInput input, IProgressMonitor monitor)
+ throws CoreException {
+ if (isConnected()) {
+ IDocumentProvider provider = SharedDocumentAdapter
+ .getDocumentProvider(input);
+ try {
+ saveDocument(provider, input, provider.getDocument(input),
+ true, monitor);
+ } finally {
+ // When we write the document, remove out hold on the buffer
+ releaseBuffer();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Release the buffer if this adapter has buffered the contents in response
+ * to a
+ * {@link #flushDocument(IDocumentProvider, IEditorInput, IDocument, boolean)}
+ * .
+ */
+ public void releaseBuffer() {
+ if (bufferedKey != null) {
+ IDocumentProvider provider = SharedDocumentAdapter
+ .getDocumentProvider(bufferedKey);
+ provider.disconnect(bufferedKey);
+ bufferedKey = null;
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.ISharedDocumentAdapter#flushDocument(org.eclipse.ui.texteditor.IDocumentProvider, org.eclipse.ui.IEditorInput, org.eclipse.jface.text.IDocument, boolean)
+ */
+ @Override
+ public void flushDocument(IDocumentProvider provider,
+ IEditorInput documentKey, IDocument document, boolean overwrite)
+ throws CoreException {
+ if (!hasBufferedContents()) {
+ // On a flush, make an extra connection to the shared document so it
+ // will be kept even
+ // if it is no longer being viewed.
+ bufferedKey = documentKey;
+ provider.connect(bufferedKey);
+ }
+ this.listener.handleDocumentFlushed();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.texteditor.IElementStateListener#elementContentAboutToBeReplaced(java.lang.Object)
+ */
+ @Override
+ public void elementContentAboutToBeReplaced(Object element) {
+ // Nothing to do
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.texteditor.IElementStateListener#elementContentReplaced(java.lang.Object)
+ */
+ @Override
+ public void elementContentReplaced(Object element) {
+ // Nothing to do
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.texteditor.IElementStateListener#elementDeleted(java.lang.Object)
+ */
+ @Override
+ public void elementDeleted(Object element) {
+ listener.handleDocumentDeleted();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.texteditor.IElementStateListener#elementDirtyStateChanged(java.lang.Object, boolean)
+ */
+ @Override
+ public void elementDirtyStateChanged(Object element, boolean isDirty) {
+ if (!isDirty) {
+ this.listener.handleDocumentSaved();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.texteditor.IElementStateListener#elementMoved(java.lang.Object, java.lang.Object)
+ */
+ @Override
+ public void elementMoved(Object originalElement, Object movedElement) {
+ // Nothing to do
+ }
+
+ /**
+ * Return whether the adapter has buffered contents. The adapter buffers
+ * contents by connecting to the document through the document provider.
+ * This means that the adapter must be disconnected either by saving or
+ * discarding the buffer.
+ *
+ * @return whether the adapter has buffered contents
+ */
+ public boolean hasBufferedContents() {
+ return bufferedKey != null;
+ }
+
+ /**
+ * Override getDocumentKey in the super class to provide an editor input
+ * when the element is a <code>LocalTypedElement</code>.
+ */
+ @Override
+ public IEditorInput getDocumentKey(Object element) {
+ if (element instanceof LocalTypedElement) {
+ LocalTypedElement localElement = (LocalTypedElement) element;
+ return localElement.getEditorInput();
+ }
+ return super.getDocumentKey(element);
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/LocalFileSaveable.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/LocalFileSaveable.java
new file mode 100644
index 000000000..d45b7936d
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/LocalFileSaveable.java
@@ -0,0 +1,479 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ * William Chen (Wind River)- [345552] Edit the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.compare;
+
+import org.eclipse.compare.CompareEditorInput;
+import org.eclipse.compare.ICompareContainer;
+import org.eclipse.compare.IContentChangeListener;
+import org.eclipse.compare.IContentChangeNotifier;
+import org.eclipse.compare.ISharedDocumentAdapter;
+import org.eclipse.compare.SharedDocumentAdapter;
+import org.eclipse.compare.contentmergeviewer.ContentMergeViewer;
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.ISafeRunnable;
+import org.eclipse.core.runtime.ListenerList;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.SafeRunner;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.tcf.te.tcf.filesystem.internal.compare.EditableSharedDocumentAdapter.ISharedDocumentAdapterListener;
+import org.eclipse.tcf.te.tcf.filesystem.internal.handlers.CacheManager;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSTreeNode;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IPropertyListener;
+import org.eclipse.ui.ISaveablesLifecycleListener;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.IWorkbenchPartConstants;
+import org.eclipse.ui.Saveable;
+import org.eclipse.ui.SaveablesLifecycleEvent;
+import org.eclipse.ui.texteditor.IDocumentProvider;
+
+/**
+ * A <code>LocalFileSaveable</code> is used as the saveable object that provides
+ * the save behavior for the merge editor input(<code>MergeEditorInput</code>).
+ */
+public class LocalFileSaveable extends Saveable implements
+ IPropertyChangeListener, ISharedDocumentAdapterListener,
+ IContentChangeListener {
+ // The property listener list.
+ private ListenerList listeners = new ListenerList(ListenerList.IDENTITY);
+
+ // The merge input that provides the left and the right compared elements.
+ private final MergeInput input;
+
+ // The input of the editor that provides the comparing view.
+ private final MergeEditorInput editorInput;
+
+ // If the current document of the file is being saved.
+ private boolean saving;
+
+ // The local file element.
+ private LocalTypedElement fileElement;
+
+ // The document provided by the local file.
+ private IDocument document;
+
+ // If the current document has been connected.
+ private boolean connected;
+
+ /**
+ * Create the file-based saveable comparison.
+ *
+ * @param input
+ * the compare input to be save
+ * @param editorInput
+ * the editor input containing the comparison
+ * @param fileElement
+ * the file element that handles the saving and change
+ * notification
+ */
+ public LocalFileSaveable(MergeInput input, MergeEditorInput editorInput, LocalTypedElement fileElement) {
+ Assert.isNotNull(input);
+
+ this.input = input;
+ this.editorInput = editorInput;
+ this.fileElement = fileElement;
+ this.fileElement.addContentChangeListener(this);
+ this.fileElement.setDocumentListener(this);
+ }
+
+ /**
+ * Dispose of the saveable.
+ */
+ public void dispose() {
+ fileElement.removeContentChangeListener(this);
+ fileElement.discardBuffer();
+ fileElement.setDocumentListener(null);
+ }
+
+ /**
+ * Performs the save.
+ *
+ * @param monitor The progress monitor.
+ *
+ * @throws CoreException If the save operation fails.
+ */
+ protected void performSave(IProgressMonitor monitor) throws CoreException {
+ try {
+ saving = true;
+ monitor.beginTask(null, 100);
+ // First, we need to flush the viewers so the changes get buffered
+ // in the input
+ flushViewers(monitor);
+ // Then we tell the input to commit its changes
+ // Only the left is ever saveable
+ if (fileElement.isDirty()) {
+ if (fileElement.isConnected()) {
+ fileElement.store2Document(monitor);
+ } else {
+ fileElement.store2Cache(monitor);
+ }
+ }
+ } finally {
+ // Make sure we fire a change for the compare input to update the
+ // viewers
+ fireInputChange();
+ setDirty(false);
+ saving = false;
+ monitor.done();
+ //Trigger upload action
+ FSTreeNode node = fileElement.getFSTreeNode();
+ CacheManager.getInstance().upload(node);
+ }
+ }
+
+ /**
+ * Flush the contents of any viewers into the compare input.
+ *
+ * @param monitor
+ * a progress monitor
+ * @throws CoreException
+ */
+ protected void flushViewers(IProgressMonitor monitor) throws CoreException {
+ editorInput.saveChanges(monitor);
+ }
+
+ /**
+ * Fire an input change for the compare input after it has been saved.
+ */
+ protected void fireInputChange() {
+ editorInput.getCompareResult().fireInputChanged();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.Saveable#isDirty()
+ */
+ @Override
+ public boolean isDirty() {
+ return editorInput.isSaveNeeded();
+ }
+
+ /**
+ * Marks the editor input dirty.
+ *
+ * @param dirty <code>True</code> if the editor is dirty, <code>false</code> if not.
+ */
+ protected void setDirty(boolean dirty) {
+ if (isDirty() != dirty) {
+ editorInput.setDirty(dirty);
+ firePropertyChange(IWorkbenchPartConstants.PROP_DIRTY);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.Saveable#getName()
+ */
+ @Override
+ public String getName() {
+ // Return the name of the file element as held in the compare input
+ if (fileElement.equals(input.getLeft())) {
+ return input.getLeft().getName();
+ }
+ if (fileElement.equals(input.getRight())) {
+ return input.getRight().getName();
+ }
+
+ // Fallback call returning name of the main non-null element of the input
+ //
+ // see org.eclipse.team.internal.ui.mapping.AbstractCompareInput#getMainElement()
+ return input.getName();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.Saveable#getToolTipText()
+ */
+ @Override
+ public String getToolTipText() {
+ return editorInput.getToolTipText();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.Saveable#getImageDescriptor()
+ */
+ @Override
+ public ImageDescriptor getImageDescriptor() {
+ Image image = input.getImage();
+ if (image != null)
+ return ImageDescriptor.createFromImage(image);
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
+ */
+ @Override
+ public void propertyChange(PropertyChangeEvent e) {
+ String propertyName = e.getProperty();
+ if (CompareEditorInput.DIRTY_STATE.equals(propertyName)) {
+ boolean changed = false;
+ Object newValue = e.getNewValue();
+ if (newValue instanceof Boolean)
+ changed = ((Boolean) newValue).booleanValue();
+
+ ContentMergeViewer cmv = (ContentMergeViewer) e.getSource();
+
+ if (fileElement.equals(input.getLeft())) {
+ if (changed && cmv.internalIsLeftDirty())
+ setDirty(changed);
+ else if (!changed && !cmv.internalIsLeftDirty()) {
+ setDirty(changed);
+ }
+ }
+ if (fileElement.equals(input.getRight())) {
+ if (changed && cmv.internalIsRightDirty())
+ setDirty(changed);
+ else if (!changed && !cmv.internalIsRightDirty()) {
+ setDirty(changed);
+ }
+ }
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.Saveable#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ if (document != null) {
+ return document.hashCode();
+ }
+ return input.hashCode();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.Saveable#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+
+ if (!(obj instanceof Saveable))
+ return false;
+
+ if (document != null) {
+ Object otherDocument = ((Saveable) obj).getAdapter(IDocument.class);
+
+ if (document == null && otherDocument == null)
+ return false;
+
+ return document != null && document.equals(otherDocument);
+ }
+
+ if (obj instanceof LocalFileSaveable) {
+ LocalFileSaveable saveable = (LocalFileSaveable) obj;
+ return saveable.input.equals(input);
+ }
+ return false;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.Saveable#getAdapter(java.lang.Class)
+ */
+ @Override
+ public Object getAdapter(Class adapter) {
+ if (adapter == IDocument.class) {
+ if (document != null)
+ return document;
+ if (fileElement.isConnected()) {
+ ISharedDocumentAdapter sda = (ISharedDocumentAdapter) fileElement
+ .getAdapter(ISharedDocumentAdapter.class);
+ if (sda != null) {
+ IEditorInput input = sda.getDocumentKey(fileElement);
+ if (input != null) {
+ IDocumentProvider provider = SharedDocumentAdapter
+ .getDocumentProvider(input);
+ if (provider != null)
+ return provider.getDocument(input);
+ }
+ }
+ }
+ }
+ if (adapter == IEditorInput.class) {
+ return fileElement.getEditorInput();
+ }
+ return super.getAdapter(adapter);
+ }
+
+ /**
+ * Get an ISaveablesLifecycleListener from the specified workbench
+ * part.
+ * @param part The workbench part.
+ * @return The listener.
+ */
+ private ISaveablesLifecycleListener getSaveablesLifecycleListener(
+ IWorkbenchPart part) {
+ if (part instanceof ISaveablesLifecycleListener)
+ return (ISaveablesLifecycleListener) part;
+
+ Object adapted = part.getAdapter(ISaveablesLifecycleListener.class);
+ if (adapted instanceof ISaveablesLifecycleListener)
+ return (ISaveablesLifecycleListener) adapted;
+
+ adapted = Platform.getAdapterManager().loadAdapter(part,
+ ISaveablesLifecycleListener.class.getName());
+ if (adapted instanceof ISaveablesLifecycleListener)
+ return (ISaveablesLifecycleListener) adapted;
+
+ return (ISaveablesLifecycleListener) part.getSite().getService(
+ ISaveablesLifecycleListener.class);
+ }
+
+ /**
+ * When the document of the local file is connected, register this saveable.
+ */
+ private void registerSaveable() {
+ ICompareContainer container = editorInput.getContainer();
+ IWorkbenchPart part = container.getWorkbenchPart();
+ if (part != null) {
+ ISaveablesLifecycleListener lifecycleListener = getSaveablesLifecycleListener(part);
+ // Remove this saveable from the lifecycle listener
+ lifecycleListener.handleLifecycleEvent(new SaveablesLifecycleEvent(
+ part, SaveablesLifecycleEvent.POST_CLOSE,
+ new Saveable[] { this }, false));
+ // Now fix the hashing so it uses the fConnected fDocument
+ IDocument document = (IDocument) getAdapter(IDocument.class);
+ if (document != null) {
+ this.document = document;
+ }
+ // Finally, add this saveable back to the listener
+ lifecycleListener.handleLifecycleEvent(new SaveablesLifecycleEvent(
+ part, SaveablesLifecycleEvent.POST_OPEN,
+ new Saveable[] { this }, false));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.tcf.te.tcf.filesystem.internal.compare.EditableSharedDocumentAdapter.ISharedDocumentAdapterListener#handleDocumentConnected()
+ */
+ @Override
+ public void handleDocumentConnected() {
+ if (connected)
+ return;
+ connected = true;
+ registerSaveable();
+ fileElement.setDocumentListener(null);
+ }
+
+ @Override
+ public void handleDocumentDeleted() {
+ // Ignore
+ }
+
+ @Override
+ public void handleDocumentDisconnected() {
+ // Ignore
+ }
+
+ @Override
+ public void handleDocumentFlushed() {
+ // Ignore
+ }
+
+ @Override
+ public void handleDocumentSaved() {
+ // Ignore
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.compare.IContentChangeListener#contentChanged(org.eclipse.compare.IContentChangeNotifier)
+ */
+ @Override
+ public void contentChanged(IContentChangeNotifier source) {
+ if (!saving) {
+ try {
+ performSave(new NullProgressMonitor());
+ } catch (CoreException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void doSave(IProgressMonitor monitor) throws CoreException {
+ if (isDirty()) {
+ performSave(monitor);
+ setDirty(false);
+ }
+ }
+
+ /**
+ * Revert any changes in the buffer back to the last saved state.
+ *
+ * @param monitor
+ * a progress monitor on <code>null</code> if progress feedback
+ * is not required
+ */
+ public void doRevert(IProgressMonitor monitor) {
+ if (!isDirty())
+ return;
+ fileElement.discardBuffer();
+ setDirty(false);
+ }
+
+ /**
+ * Add a property change listener. Adding a listener that is already
+ * registered has no effect.
+ *
+ * @param listener
+ * the listener
+ */
+ public void addPropertyListener(IPropertyListener listener) {
+ listeners.add(listener);
+ }
+
+ /**
+ * Remove a property change listener. Removing a listener that is not
+ * registered has no effect.
+ *
+ * @param listener
+ * the listener
+ */
+ public void removePropertyListener(IPropertyListener listener) {
+ listeners.remove(listener);
+ }
+
+ /**
+ * Fire a property change event for this buffer.
+ *
+ * @param property
+ * the property that changed
+ */
+ protected void firePropertyChange(final int property) {
+ Object[] allListeners = listeners.getListeners();
+ for (int i = 0; i < allListeners.length; i++) {
+ final Object object = allListeners[i];
+ SafeRunner.run(new ISafeRunnable() {
+ @Override
+ public void run() throws Exception {
+ ((IPropertyListener) object).propertyChanged(
+ LocalFileSaveable.this, property);
+ }
+
+ @Override
+ public void handleException(Throwable exception) {
+ // handled by platform
+ }
+ });
+ }
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/LocalTypedElement.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/LocalTypedElement.java
new file mode 100644
index 000000000..3884287b9
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/LocalTypedElement.java
@@ -0,0 +1,306 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ * William Chen (Wind River)- [345552] Edit the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.compare;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.compare.IEditableContent;
+import org.eclipse.compare.ISharedDocumentAdapter;
+import org.eclipse.compare.ITypedElement;
+import org.eclipse.core.filesystem.EFS;
+import org.eclipse.core.filesystem.IFileStore;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.tcf.te.tcf.filesystem.activator.UIPlugin;
+import org.eclipse.tcf.te.tcf.filesystem.internal.compare.EditableSharedDocumentAdapter.ISharedDocumentAdapterListener;
+import org.eclipse.tcf.te.tcf.filesystem.internal.handlers.CacheManager;
+import org.eclipse.tcf.te.tcf.filesystem.internal.nls.Messages;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSTreeNode;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.ide.FileStoreEditorInput;
+
+/**
+ * A <code>LocalTypedElement</code> extends <code>MergeTypedElement</code> and
+ * wraps an <code>FSTreeNode</code> so that it can be used as the left element
+ * of a <code>MergeEditorInput</code>. It implements the interface
+ * <code>IEditableContent</code> so that it is editable.
+ */
+public class LocalTypedElement extends MergeTypedElement implements
+ IEditableContent, IAdaptable, ISharedDocumentAdapterListener {
+ // If the current edited file is dirty.
+ private boolean dirty;
+ // The shared document adapter
+ private EditableSharedDocumentAdapter documentAdapter;
+ // The shared document listener.
+ private ISharedDocumentAdapterListener documentListener;
+
+ /**
+ * Creates a <code>LocalTypedElement</code> for the given resource.
+ *
+ * @param resource
+ * the resource
+ */
+ public LocalTypedElement(FSTreeNode node) {
+ super(node);
+ setContent(getContent());
+ dirty = false;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
+ */
+ @Override
+ public Object getAdapter(Class adapter) {
+ if (adapter == ISharedDocumentAdapter.class) {
+ if (documentAdapter == null)
+ documentAdapter = new EditableSharedDocumentAdapter(this);
+ return documentAdapter;
+ }
+ return Platform.getAdapterManager().getAdapter(this, adapter);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.BufferedContent#setContent(byte[])
+ */
+ @Override
+ public void setContent(byte[] contents) {
+ dirty = true;
+ super.setContent(contents);
+ }
+
+ /**
+ * Set the document listener.
+ *
+ * @param documentListener
+ * the document listener.
+ */
+ public void setDocumentListener(
+ ISharedDocumentAdapterListener documentListener) {
+ this.documentListener = documentListener;
+ }
+
+ /**
+ * Return an input stream that opens that locally cached file to provide the
+ * content.
+ *
+ * @return a buffered input stream containing the contents of this file
+ * @exception CoreException
+ * if the contents of this storage could not be accessed
+ */
+ @Override
+ protected InputStream createStream() throws CoreException {
+ try {
+ IPath cachePath = CacheManager.getInstance().getCachePath(node);
+ File cacheFile = cachePath.toFile();
+ return new BufferedInputStream(new FileInputStream(cacheFile));
+ } catch (FileNotFoundException e) {
+ IStatus error = new Status(IStatus.ERROR,
+ UIPlugin.getUniqueIdentifier(), e.getLocalizedMessage(), e);
+ throw new CoreException(error);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.IEditableContent#isEditable()
+ */
+ @Override
+ public boolean isEditable() {
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.IEditableContent#replace(org.eclipse.compare.ITypedElement, org.eclipse.compare.ITypedElement)
+ */
+ @Override
+ public ITypedElement replace(ITypedElement dest, ITypedElement src) {
+ return dest;
+ }
+
+ /**
+ * Save the shared document for this element. The save can only be performed
+ * if the element is connected to a shared document. If the element is not
+ * connected, <code>false</code> is returned.
+ *
+ * @param overwrite
+ * indicates whether overwrite should be performed while saving
+ * the given element if necessary
+ * @param monitor
+ * a progress monitor
+ * @throws CoreException
+ */
+ public boolean store2Document(IProgressMonitor monitor)
+ throws CoreException {
+ if (isConnected()) {
+ IEditorInput input = documentAdapter.getDocumentKey(this);
+ documentAdapter.saveDocument(input, monitor);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Judges whether the content has been changed.
+ *
+ * @return
+ */
+ public boolean isDirty() {
+ return dirty
+ || (documentAdapter != null && documentAdapter
+ .hasBufferedContents());
+ }
+
+ /**
+ * Set the dirty state.
+ *
+ * @param dirty
+ * The dirty state.
+ */
+ public void setDirty(boolean dirty) {
+ this.dirty = dirty;
+ }
+
+ /**
+ * If the document adapter has been connected.
+ *
+ * @return true if it is not null and connected.
+ */
+ public boolean isConnected() {
+ return documentAdapter != null && documentAdapter.isConnected();
+ }
+
+ /**
+ * Return the path to the local file of this node. It is used to compute its
+ * hash code and as the title of the comparison editor.
+ */
+ @Override
+ public String toString() {
+ File cacheFile = CacheManager.getInstance().getCacheFile(node);
+ return cacheFile.toString();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.te.tcf.filesystem.internal.compare.EditableSharedDocumentAdapter.ISharedDocumentAdapterListener#handleDocumentConnected()
+ */
+ @Override
+ public void handleDocumentConnected() {
+ if (documentListener != null)
+ documentListener.handleDocumentConnected();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.te.tcf.filesystem.internal.compare.EditableSharedDocumentAdapter.ISharedDocumentAdapterListener#handleDocumentDeleted()
+ */
+ @Override
+ public void handleDocumentDeleted() {
+ if (documentListener != null)
+ documentListener.handleDocumentDeleted();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.te.tcf.filesystem.internal.compare.EditableSharedDocumentAdapter.ISharedDocumentAdapterListener#handleDocumentDisconnected()
+ */
+ @Override
+ public void handleDocumentDisconnected() {
+ if (documentListener != null)
+ documentListener.handleDocumentDisconnected();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.te.tcf.filesystem.internal.compare.EditableSharedDocumentAdapter.ISharedDocumentAdapterListener#handleDocumentFlushed()
+ */
+ @Override
+ public void handleDocumentFlushed() {
+ fireContentChanged();
+ if (documentListener != null)
+ documentListener.handleDocumentFlushed();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.te.tcf.filesystem.internal.compare.EditableSharedDocumentAdapter.ISharedDocumentAdapterListener#handleDocumentSaved()
+ */
+ @Override
+ public void handleDocumentSaved() {
+ if (documentListener != null)
+ documentListener.handleDocumentSaved();
+ }
+
+ /**
+ * Get an editor input for this file.
+ *
+ * @return The editor input.
+ */
+ public IEditorInput getEditorInput() {
+ IPath path = CacheManager.getInstance().getCachePath(node);
+ IFileStore fileStore = EFS.getLocalFileSystem().getStore(path);
+ return new FileStoreEditorInput(fileStore);
+ }
+
+ /**
+ * Save to its local file when the document has not been connected yet.
+ *
+ * @param monitor
+ * The monitor that reports the progress.
+ */
+ public void store2Cache(IProgressMonitor monitor) throws CoreException {
+ File cacheFile = CacheManager.getInstance().getCacheFile(node);
+ monitor.beginTask(Messages.LocalTypedElement_SavingFile + cacheFile.getName(), 100);
+ InputStream is = getContents();
+ BufferedOutputStream bos = null;
+ try {
+ long total = cacheFile.length();
+ bos = new BufferedOutputStream(new FileOutputStream(cacheFile));
+ byte[] data = new byte[10 * 1024];
+ int length;
+ long current = 0;
+ int currProgress = 0;
+ while ((length = is.read(data)) > 0) {
+ bos.write(data, 0, length);
+ bos.flush();
+ current += length;
+ int progress = (int) (current * 100 / total);
+ if (currProgress != progress) {
+ monitor.worked(progress - currProgress);
+ currProgress = progress;
+ }
+ }
+ setDirty(false);
+ } catch (IOException e) {
+ throw new CoreException(new Status(IStatus.ERROR,
+ UIPlugin.getUniqueIdentifier(), e.getLocalizedMessage(), e));
+ } finally {
+ if (is != null)
+ try {
+ is.close();
+ } catch (IOException ex) {
+ }
+ if (bos != null) {
+ try {
+ bos.close();
+ } catch (Exception e) {
+ }
+ }
+ // Notify the local file element that the document has changed.
+ fireContentChanged();
+ }
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/MergeEditorInput.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/MergeEditorInput.java
new file mode 100644
index 000000000..eea67060f
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/MergeEditorInput.java
@@ -0,0 +1,409 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ * William Chen (Wind River)- [345552] Edit the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.compare;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.compare.CompareEditorInput;
+import org.eclipse.compare.IPropertyChangeNotifier;
+import org.eclipse.compare.structuremergeviewer.Differencer;
+import org.eclipse.compare.structuremergeviewer.ICompareInput;
+import org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener;
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.ISafeRunnable;
+import org.eclipse.core.runtime.ListenerList;
+import org.eclipse.core.runtime.SafeRunner;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.osgi.util.TextProcessor;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.tcf.te.tcf.filesystem.activator.UIPlugin;
+import org.eclipse.tcf.te.tcf.filesystem.internal.ImageConsts;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IPropertyListener;
+import org.eclipse.ui.ISaveablesSource;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchPartConstants;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.Saveable;
+/**
+ * A <code>MergeEditorInput</code> is the input of a compare editor used to
+ * compare the local file and the remote file in a Target Explorer file system.
+ */
+public class MergeEditorInput extends CompareEditorInput implements
+ ISaveablesSource, IPropertyListener, ICompareInputChangeListener {
+
+ // The left element is a local file which can be editable.
+ private LocalTypedElement left;
+ // The right element is the remote file which is read only.
+ private RemoteTypedElement right;
+
+ // The active page of the current workbench.
+ private final IWorkbenchPage page;
+
+ // The current input change listener list.
+ private final ListenerList inputChangeListeners = new ListenerList(ListenerList.IDENTITY);
+ // The current saveable for the left element, i.e., the local file.
+ private LocalFileSaveable saveable;
+
+ /**
+ * Creates a new MergeEditorInput.
+ *
+ * @param left
+ * @param right
+ * @param page
+ */
+ public MergeEditorInput(LocalTypedElement left, RemoteTypedElement right, IWorkbenchPage page) {
+ super(new CompareConfiguration());
+ this.page = page;
+ this.left = left;
+ this.right = right;
+ configureCompare();
+ }
+
+ /**
+ * Configure the compare configuration using the left
+ * and right elements. Set the left and right label.
+ * Set the editable status.
+ */
+ protected void configureCompare() {
+ CompareConfiguration cc = getCompareConfiguration();
+ cc.setLeftEditable(true);
+ cc.setRightEditable(false);
+
+ String name = TextProcessor.process(left.getName());
+ String label = "Local: " + name; //$NON-NLS-1$
+ cc.setLeftLabel(label);
+
+ name = TextProcessor.process(right.toString());
+ label = "Remote: " + name; //$NON-NLS-1$
+ cc.setRightLabel(label);
+ }
+
+ /**
+ * Returns <code>true</code> if the other object is of type
+ * <code>MergeEditorInput</code> and both of their corresponding fLeft and
+ * fRight objects are identical. The content is not considered.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this)
+ return true;
+ if (obj instanceof MergeEditorInput) {
+ MergeEditorInput other = (MergeEditorInput) obj;
+ return other.left.equals(left) && other.right.equals(right);
+ }
+ return false;
+ }
+
+ /**
+ * Prepare the compare input of this editor input. This method is not
+ * intended to be overridden of extended by subclasses (but is not final for
+ * backwards compatibility reasons). The implementation of this method in
+ * this class delegates the creation of the compare input to the
+ * {@link #prepareCompareInput(IProgressMonitor)} method which subclasses
+ * must implement.
+ *
+ * @see org.eclipse.compare.CompareEditorInput#prepareInput(org.eclipse.core.runtime.IProgressMonitor)
+ */
+ @Override
+ protected Object prepareInput(IProgressMonitor monitor)
+ throws InvocationTargetException, InterruptedException {
+ right.cacheContents(monitor);
+ MergeInput input = new MergeInput(left, right);
+ setTitle(input.getName());
+ return input;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.CompareEditorInput#getToolTipText()
+ */
+ @Override
+ public String getToolTipText() {
+ return "Compare " + left + " and " + right; //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.CompareEditorInput#getTitle()
+ */
+ @Override
+ public String getTitle() {
+ return "Compare " + left.getName() + " with Local Cache"; //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ /**
+ * Override the super method to provide clear typed input.
+ */
+ @Override
+ public MergeInput getCompareResult() {
+ return (MergeInput) super.getCompareResult();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.CompareEditorInput#handleDispose()
+ */
+ @Override
+ protected void handleDispose() {
+ super.handleDispose();
+ ICompareInput compareInput = getCompareResult();
+ if (compareInput != null)
+ compareInput.removeCompareInputChangeListener(this);
+ if(getCompareResult()!=null){
+ getSaveable().removePropertyListener(this);
+ getSaveable().dispose();
+ }
+ left.discardBuffer();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.CompareEditorInput#contentsCreated()
+ */
+ @Override
+ protected void contentsCreated() {
+ super.contentsCreated();
+ if (getCompareResult() != null) {
+ getCompareResult().addCompareInputChangeListener(this);
+ getSaveable().addPropertyListener(this);
+ setDirty(getSaveable().isDirty());
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.IPropertyListener#propertyChanged(java.lang.Object, int)
+ */
+ @Override
+ public void propertyChanged(Object source, int propId) {
+ if (propId == IWorkbenchPartConstants.PROP_DIRTY && getCompareResult()!=null) {
+ setDirty(getSaveable().isDirty());
+ }
+ }
+
+ /**
+ * Close the editor if it is not dirty. If it is still dirty, let the
+ * content merge viewer handle the compare input change.
+ *
+ * @param checkForUnsavedChanges
+ * whether to check for unsaved changes
+ * @return <code>true</code> if the editor was closed (note that the close
+ * may be asynchronous)
+ */
+ protected boolean closeEditor(boolean checkForUnsavedChanges) {
+ if (isSaveNeeded() && checkForUnsavedChanges) {
+ return false;
+ }
+ final IWorkbenchPage page = getPage();
+ if (page == null)
+ return false;
+
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ Shell shell = page.getWorkbenchWindow().getShell();
+ if (shell == null)
+ return;
+
+ IEditorPart part = page.findEditor(MergeEditorInput.this);
+ getPage().closeEditor(part, false);
+ }
+ };
+ if (Display.getCurrent() != null) {
+ runnable.run();
+ } else {
+ Shell shell = page.getWorkbenchWindow().getShell();
+ if (shell == null)
+ return false;
+ Display display = shell.getDisplay();
+ display.asyncExec(runnable);
+ }
+ return true;
+ }
+
+ /**
+ * Get the current active page.
+ * @return the workbench page.
+ */
+ IWorkbenchPage getPage() {
+ if (page == null)
+ return PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
+ return page;
+ }
+
+ /**
+ * Propagate the input change event to the compare input change listener list.
+ */
+ /* default */void propogateInputChange() {
+ if (!inputChangeListeners.isEmpty()) {
+ Object[] allListeners = inputChangeListeners.getListeners();
+ for (int i = 0; i < allListeners.length; i++) {
+ final ICompareInputChangeListener listener = (ICompareInputChangeListener) allListeners[i];
+ SafeRunner.run(new ISafeRunnable() {
+ @Override
+ public void run() throws Exception {
+ listener.compareInputChanged(getCompareResult());
+ }
+
+ @Override
+ public void handleException(Throwable exception) {
+ // Logged by the safe runner
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * Get the fSaveable that provides the save behavior for this compare editor
+ * input. The {@link #createSaveable()} is called to create the fSaveable if
+ * it does not yet exist. This method cannot be called until after the input
+ * is prepared (i.e. until after the {@link #run(IProgressMonitor)} method
+ * is called which will in turn will invoke
+ * {@link #prepareCompareInput(IProgressMonitor)}.
+ *
+ * @return fSaveable that provides the save behavior for this compare editor
+ * input.
+ */
+ public LocalFileSaveable getSaveable() {
+ if (saveable == null) {
+ Assert.isNotNull(getCompareResult());
+ saveable = new LocalFileSaveable(getCompareResult(), this, left);
+ }
+ return saveable;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.ISaveablesSource#getActiveSaveables()
+ */
+ @Override
+ public Saveable[] getActiveSaveables() {
+ if (getCompareResult() == null)
+ return new Saveable[0];
+ return new Saveable[] { getSaveable() };
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.ISaveablesSource#getSaveables()
+ */
+ @Override
+ public Saveable[] getSaveables() {
+ return getActiveSaveables();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.CompareEditorInput#addCompareInputChangeListener(org.eclipse.compare.structuremergeviewer.ICompareInput, org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener)
+ */
+ @Override
+ public void addCompareInputChangeListener(ICompareInput input,
+ ICompareInputChangeListener listener) {
+ if (input == getCompareResult()) {
+ inputChangeListeners.add(listener);
+ } else {
+ super.addCompareInputChangeListener(input, listener);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.CompareEditorInput#removeCompareInputChangeListener(org.eclipse.compare.structuremergeviewer.ICompareInput, org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener)
+ */
+ @Override
+ public void removeCompareInputChangeListener(ICompareInput input,
+ ICompareInputChangeListener listener) {
+ if (input == getCompareResult()) {
+ inputChangeListeners.remove(listener);
+ } else {
+ super.removeCompareInputChangeListener(input, listener);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.CompareEditorInput#getTitleImage()
+ */
+ @Override
+ public Image getTitleImage() {
+ return UIPlugin.getImage(ImageConsts.COMPARE_EDITOR);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.CompareEditorInput#getImageDescriptor()
+ */
+ @Override
+ public ImageDescriptor getImageDescriptor() {
+ return UIPlugin.getImageDescriptor(ImageConsts.COMPARE_EDITOR);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.CompareEditorInput#findContentViewer(org.eclipse.jface.viewers.Viewer, org.eclipse.compare.structuremergeviewer.ICompareInput, org.eclipse.swt.widgets.Composite)
+ */
+ @Override
+ public Viewer findContentViewer(Viewer oldViewer, ICompareInput input,
+ Composite parent) {
+ Viewer newViewer = super.findContentViewer(oldViewer, input, parent);
+ boolean isNewViewer = newViewer != oldViewer;
+ if (isNewViewer && newViewer instanceof IPropertyChangeNotifier && getCompareResult()!=null) {
+ // Register the model for change events if appropriate
+ final IPropertyChangeNotifier dsp = (IPropertyChangeNotifier) newViewer;
+ dsp.addPropertyChangeListener(getSaveable());
+ Control c = newViewer.getControl();
+ c.addDisposeListener(new DisposeListener() {
+ @Override
+ public void widgetDisposed(DisposeEvent e) {
+ dsp.removePropertyChangeListener(getSaveable());
+ }
+ });
+ }
+ return newViewer;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.CompareEditorInput#canRunAsJob()
+ */
+ @Override
+ public boolean canRunAsJob() {
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.CompareEditorInput#isDirty()
+ */
+ @Override
+ public boolean isDirty() {
+ if (saveable != null)
+ return saveable.isDirty();
+ return super.isDirty();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener#compareInputChanged(org.eclipse.compare.structuremergeviewer.ICompareInput)
+ */
+ @Override
+ public void compareInputChanged(ICompareInput source) {
+ if (source == getCompareResult()) {
+ boolean closed = false;
+ if (source.getKind() == Differencer.NO_CHANGE) {
+ closed = closeEditor(true);
+ }
+ if (!closed) {
+ // The editor was closed either because the compare
+ // input still has changes or because the editor input
+ // is dirty. In either case, fire the changes
+ // to the registered listeners
+ propogateInputChange();
+ }
+ }
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/MergeInput.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/MergeInput.java
new file mode 100644
index 000000000..db6f077b3
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/MergeInput.java
@@ -0,0 +1,158 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ * William Chen (Wind River)- [345552] Edit the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.compare;
+
+import org.eclipse.compare.ITypedElement;
+import org.eclipse.compare.structuremergeviewer.Differencer;
+import org.eclipse.compare.structuremergeviewer.ICompareInput;
+import org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener;
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.ISafeRunnable;
+import org.eclipse.core.runtime.ListenerList;
+import org.eclipse.core.runtime.SafeRunner;
+import org.eclipse.swt.graphics.Image;
+
+/**
+ * An abstract compare input whose purpose is to support change notification
+ * through a {@link CompareInputChangeNotifier}.
+ */
+public class MergeInput implements ICompareInput {
+
+ // The left element.
+ private ITypedElement left;
+ // The right element.
+ private ITypedElement right;
+ // The compare input change listener list.
+ private final ListenerList listeners = new ListenerList(ListenerList.IDENTITY);
+
+ /**
+ * Create a <code>MergeInput</code>.
+ * @param left the left element.
+ * @param right the right element.
+ */
+ public MergeInput(ITypedElement left, ITypedElement right) {
+ this.left = left;
+ this.right = right;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.structuremergeviewer.ICompareInput#addCompareInputChangeListener(org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener)
+ */
+ @Override
+ public void addCompareInputChangeListener(ICompareInputChangeListener listener) {
+ listeners.add(listener);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.structuremergeviewer.ICompareInput#removeCompareInputChangeListener(org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener)
+ */
+ @Override
+ public void removeCompareInputChangeListener(ICompareInputChangeListener listener) {
+ listeners.remove(listener);
+ }
+
+ /**
+ * Fire a compare input change event. This method must be called from the UI
+ * thread.
+ */
+ void fireInputChanged() {
+ if (!listeners.isEmpty()) {
+ Object[] _listeners = listeners.getListeners();
+ for (int i = 0; i < _listeners.length; i++) {
+ final ICompareInputChangeListener listener = (ICompareInputChangeListener) _listeners[i];
+ SafeRunner.run(new ISafeRunnable() {
+ @Override
+ public void run() throws Exception {
+ listener.compareInputChanged(MergeInput.this);
+ }
+
+ @Override
+ public void handleException(Throwable exception) {
+ // Logged by the safe runner
+ }
+ });
+ }
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.structuremergeviewer.ICompareInput#copy(boolean)
+ */
+ @Override
+ public void copy(boolean leftToRight) {
+ Assert.isTrue(false, "Copy is not support by this type of compare input"); //$NON-NLS-1$
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.structuremergeviewer.ICompareInput#getAncestor()
+ */
+ @Override
+ public ITypedElement getAncestor() {
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.structuremergeviewer.ICompareInput#getImage()
+ */
+ @Override
+ public Image getImage() {
+ ITypedElement element = getMainElement();
+ return element == null ? null : element.getImage();
+ }
+
+ /**
+ * Return the main non-null element that identifies this input. By default,
+ * the fLeft is returned if non-null. If the fLeft is null, the fRight is
+ * returned. If both the fLeft and fRight are null the ancestor is returned.
+ *
+ * @return the main non-null element that identifies this input
+ */
+ private ITypedElement getMainElement() {
+ if (left != null)
+ return left;
+ if (right != null)
+ return right;
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.structuremergeviewer.ICompareInput#getKind()
+ */
+ @Override
+ public int getKind() {
+ return Differencer.CHANGE;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.structuremergeviewer.ICompareInput#getLeft()
+ */
+ @Override
+ public ITypedElement getLeft() {
+ return left;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.structuremergeviewer.ICompareInput#getName()
+ */
+ @Override
+ public String getName() {
+ ITypedElement element = getMainElement();
+ return element == null ? null : element.getName();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.structuremergeviewer.ICompareInput#getRight()
+ */
+ @Override
+ public ITypedElement getRight() {
+ return right;
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/MergeTypedElement.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/MergeTypedElement.java
new file mode 100644
index 000000000..0524461b6
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/MergeTypedElement.java
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ * William Chen (Wind River)- [345552] Edit the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.compare;
+
+import org.eclipse.compare.BufferedContent;
+import org.eclipse.compare.CompareUI;
+import org.eclipse.compare.ITypedElement;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSTreeNode;
+
+/**
+ * A <code>MergeTypedElement</code> wraps an <code>FSTreeNode</code> so that it
+ * can be used as input for the differencing engine (<code>ITypedElement</code>).
+ */
+public abstract class MergeTypedElement extends BufferedContent implements ITypedElement {
+ // The File System tree node to be wrapped.
+ protected FSTreeNode node;
+
+ /**
+ * Create a MergeTypedElement for the given node.
+ *
+ * @param node
+ * The node.
+ */
+ public MergeTypedElement(FSTreeNode node) {
+ this.node = node;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.ITypedElement#getImage()
+ */
+ @Override
+ public Image getImage() {
+ return CompareUI.getImage(getType());
+ }
+
+ /**
+ * Return the tree node wrapped.
+ *
+ * @return The tree node of the file
+ */
+ public FSTreeNode getFSTreeNode() {
+ return node;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.ITypedElement#getType()
+ */
+ @Override
+ public String getType() {
+ if (node.isDirectory())
+ return ITypedElement.FOLDER_TYPE;
+ if (node != null) {
+ String s = node.name;
+ int dot = s.lastIndexOf('.');
+ if (dot != -1)
+ s = s.substring(dot + 1);
+ if (s != null)
+ return s;
+ }
+ return ITypedElement.UNKNOWN_TYPE;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof ITypedElement) {
+ return toString().equals(other.toString());
+ }
+ return super.equals(other);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.ITypedElement#getName()
+ */
+ @Override
+ public String getName() {
+ return node.name;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ *
+ * Returns the hash code of the name.
+ */
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/RemoteTypedElement.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/RemoteTypedElement.java
new file mode 100644
index 000000000..d346c0fc9
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/compare/RemoteTypedElement.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ * William Chen (Wind River)- [345552] Edit the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.compare;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.tcf.te.tcf.filesystem.activator.UIPlugin;
+import org.eclipse.tcf.te.tcf.filesystem.internal.handlers.CacheManager;
+import org.eclipse.tcf.te.tcf.filesystem.internal.nls.Messages;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSTreeNode;
+
+/**
+ * A <code>RemoteTypedElement</code> wraps an <code>FSTreeNode</code> so that it
+ * can be used as input for the differencing engine (<code>ITypedElement</code>)
+ * as the right element of the comparison editor.
+ *
+ * @since 3.7
+ */
+public class RemoteTypedElement extends MergeTypedElement {
+ /**
+ * Creates a <code>RemoteTypedElement</code> for the given node.
+ *
+ * @param node
+ * the tree node.
+ */
+ public RemoteTypedElement(FSTreeNode node) {
+ super(node);
+ }
+
+ /**
+ * Return an input stream that opens that remote file to provide the stream
+ * content.
+ *
+ * @return a buffered input stream containing the contents of this file
+ * @exception CoreException
+ * if the contents of this storage could not be accessed
+ */
+ @Override
+ protected InputStream createStream() throws CoreException {
+ try {
+ return node.getLocationURL().openStream();
+ } catch (IOException e) {
+ Status error = new Status(IStatus.ERROR,
+ UIPlugin.getUniqueIdentifier(), e.getLocalizedMessage(), e);
+ throw new CoreException(error);
+ }
+ }
+
+ /**
+ * Download the remote file and save the content so that it is cached for
+ * getContents call.
+ *
+ * @param monitor
+ * The monitor used to display downloading progress.
+ * @throws InvocationTargetException
+ * throws when an exception occurs during downloading.
+ */
+ public void cacheContents(IProgressMonitor monitor)
+ throws InvocationTargetException {
+ monitor.beginTask(
+ NLS.bind(Messages.CacheManager_DowloadingFile, node.name), 100);
+ OutputStream output = null;
+ try {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ output = new BufferedOutputStream(baos);
+ monitor.beginTask(Messages.RemoteTypedElement_GettingRemoteContent
+ + node.name, 100);
+ CacheManager.getInstance().download2OutputStream(node, output, monitor);
+ if (!monitor.isCanceled()) {
+ setContent(baos.toByteArray());
+ }
+ } catch (IOException e) {
+ throw new InvocationTargetException(e);
+ }
+ }
+
+ /**
+ * Return the external form of the URL to the remote file of this node. It
+ * is used to compute its hash code and as the title of the comparison
+ * editor.
+ */
+ @Override
+ public String toString() {
+ return node.getLocationURL().toString();
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/events/INodeStateListener.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/events/INodeStateListener.java
new file mode 100644
index 000000000..c927ba574
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/events/INodeStateListener.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ * William Chen (Wind River)- [345552] Edit the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.events;
+
+import org.eclipse.tcf.te.tcf.filesystem.model.FSTreeNode;
+/**
+ * An INodeStateListener is a listener interface. Classes that implement this
+ * interface serve as a listener processing the event that a node state has changed.
+ *
+ */
+public interface INodeStateListener {
+ /**
+ * Fired when the state of the specified FSTreeNode has changed.
+ *
+ * @param node The FSTreeNode whose state has changed.
+ */
+ void stateChanged(FSTreeNode node);
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/exceptions/TCFChannelException.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/exceptions/TCFChannelException.java
new file mode 100644
index 000000000..cfc089bd9
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/exceptions/TCFChannelException.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ * William Chen (Wind River)- [345552] Edit the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.exceptions;
+
+/**
+ * TCF channel exception.
+ */
+public class TCFChannelException extends TCFException {
+ private static final long serialVersionUID = 7414816212710485160L;
+
+ /**
+ * Constructor.
+ *
+ * @param message
+ * The exception detail message or <code>null</code>.
+ */
+ public TCFChannelException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param message
+ * The exception detail message or <code>null</code>.
+ * @param cause
+ * The exception cause or <code>null</code>.
+ */
+ public TCFChannelException(String message, Throwable cause){
+ super(message, cause);
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/exceptions/TCFException.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/exceptions/TCFException.java
new file mode 100644
index 000000000..2ec9120fe
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/exceptions/TCFException.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ * William Chen (Wind River)- [345552] Edit the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.exceptions;
+
+/**
+ * TCF file system implementation base exception.
+ */
+public class TCFException extends Exception {
+ private static final long serialVersionUID = -220092425137980661L;
+
+ /**
+ * Constructor.
+ *
+ * @param message
+ * The exception detail message or <code>null</code>.
+ */
+ public TCFException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param message
+ * The exception detail message or <code>null</code>.
+ * @param cause
+ * The exception cause or <code>null</code>.
+ */
+ public TCFException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/exceptions/TCFFileSystemException.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/exceptions/TCFFileSystemException.java
new file mode 100644
index 000000000..c6857422a
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/exceptions/TCFFileSystemException.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ * William Chen (Wind River)- [345552] Edit the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.exceptions;
+
+/**
+ * TCF remote file system exception.
+ */
+public class TCFFileSystemException extends TCFException {
+ private static final long serialVersionUID = -5203855887734608373L;
+
+ /**
+ * Constructor.
+ *
+ * @param message
+ * The exception detail message or <code>null</code>.
+ */
+ public TCFFileSystemException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param message
+ * The exception detail message or <code>null</code>.
+ * @param cause
+ * The exception cause or <code>null</code>.
+ */
+ public TCFFileSystemException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/explorer/FSExplorerEditorPage.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/explorer/FSExplorerEditorPage.java
new file mode 100644
index 000000000..5e8ef04fe
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/explorer/FSExplorerEditorPage.java
@@ -0,0 +1,134 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.explorer;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.tcf.te.tcf.filesystem.controls.FSTreeControl;
+import org.eclipse.tcf.te.tcf.filesystem.internal.help.IContextHelpIds;
+import org.eclipse.tcf.te.tcf.filesystem.internal.nls.Messages;
+import org.eclipse.tcf.te.ui.forms.CustomFormToolkit;
+import org.eclipse.tcf.te.ui.views.editor.AbstractCustomFormToolkitEditorPage;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.forms.IManagedForm;
+import org.eclipse.ui.forms.widgets.ExpandableComposite;
+import org.eclipse.ui.forms.widgets.Section;
+import org.eclipse.ui.forms.widgets.TableWrapData;
+
+
+/**
+ * File system editor page implementation.
+ */
+public class FSExplorerEditorPage extends AbstractCustomFormToolkitEditorPage {
+ // The references to the pages subcontrol's (needed for disposal)
+ private FSTreeControl fileSystemControl;
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.forms.editor.FormPage#dispose()
+ */
+ @Override
+ public void dispose() {
+ if (fileSystemControl != null) { fileSystemControl.dispose(); fileSystemControl = null; }
+ super.dispose();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.forms.editor.FormPage#createFormContent(org.eclipse.ui.forms.IManagedForm)
+ */
+ @Override
+ protected void createFormContent(IManagedForm managedForm) {
+ super.createFormContent(managedForm);
+
+ // Configure the managed form
+ configureManagedForm(managedForm);
+
+ // Do create the content of the form now
+ doCreateFormContent(managedForm.getForm().getBody(), getFormToolkit());
+
+ // Re-arrange the controls
+ managedForm.reflow(true);
+ }
+
+ /**
+ * Configure the managed form to be ready for usage.
+ *
+ * @param managedForm The managed form. Must not be <code>null</code>.
+ */
+ protected void configureManagedForm(IManagedForm managedForm) {
+ Assert.isNotNull(managedForm);
+
+ // Configure main layout
+ Composite body = managedForm.getForm().getBody();
+ GridLayout layout = new GridLayout();
+ layout.marginHeight = 2; layout.marginWidth = 0;
+ body.setLayout(layout);
+ body.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB));
+
+ // Set context help id
+ PlatformUI.getWorkbench().getHelpSystem().setHelp(managedForm.getForm(), IContextHelpIds.FS_EXPLORER_EDITOR_PAGE);
+ }
+
+ /**
+ * Do create the managed form content.
+ *
+ * @param parent The parent composite. Must not be <code>null</code>
+ * @param toolkit The {@link CustomFormToolkit} instance. Must not be <code>null</code>.
+ */
+ protected void doCreateFormContent(Composite parent, CustomFormToolkit toolkit) {
+ Assert.isNotNull(parent);
+ Assert.isNotNull(toolkit);
+
+ Section section = toolkit.getFormToolkit().createSection(parent, ExpandableComposite.TITLE_BAR);
+ String title = Messages.FSExplorerTreeControl_section_title;
+ // Stretch to a length of 40 characters to make sure the title can be changed
+ // to hold and show text up to this length
+ while (title.length() < 40) {
+ title += " "; //$NON-NLS-1$
+ }
+ // Set the title to the section
+ section.setText(title);
+ section.setLayoutData(new GridData(GridData.FILL_BOTH));
+
+ // Create the client area
+ Composite client = toolkit.getFormToolkit().createComposite(section);
+ GridLayout layout = new GridLayout();
+ layout.marginWidth = 0; layout.marginHeight = 0;
+ client.setLayout(layout);
+ section.setClient(client);
+
+ // Setup the file system tree control
+ fileSystemControl = doCreateFileSystemTreeControl();
+ Assert.isNotNull(fileSystemControl);
+ fileSystemControl.setupFormPanel((Composite)section.getClient(), toolkit);
+
+ // Set the initial input
+ fileSystemControl.getViewer().setInput(getEditorInputNode());
+ }
+
+ /**
+ * Creates and returns a file system tree control.
+ *
+ * @return The new file system tree control.
+ */
+ protected FSTreeControl doCreateFileSystemTreeControl() {
+ return new FSTreeControl(this);
+ }
+
+ /**
+ * Returns the associated file system tree control.
+ *
+ * @return The associated file system tree control or <code>null</code>.
+ */
+ protected final FSTreeControl getFileSystemTreeControl() {
+ return fileSystemControl;
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/CacheManager.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/CacheManager.java
new file mode 100644
index 000000000..2da3b1136
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/CacheManager.java
@@ -0,0 +1,467 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ * William Chen (Wind River)- [345387] Open the remote files with a proper editor
+ * William Chen (Wind River)- [345552] Edit the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.handlers;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.net.URL;
+import java.text.DecimalFormat;
+
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.tcf.te.tcf.filesystem.activator.UIPlugin;
+import org.eclipse.tcf.te.tcf.filesystem.internal.exceptions.TCFException;
+import org.eclipse.tcf.te.tcf.filesystem.internal.nls.Messages;
+import org.eclipse.tcf.te.tcf.filesystem.internal.url.TcfURLConnection;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSTreeNode;
+import org.eclipse.ui.PlatformUI;
+
+/**
+ * The local file system cache used to manage the temporary files downloaded
+ * from a remote file system.
+ */
+public class CacheManager {
+ // The agent directory's prefixed name.
+ private static final String WS_AGENT_DIR_PREFIX = "agent_"; //$NON-NLS-1$
+
+ // The default chunk size of the buffer used during downloading files.
+ private static final int DEFAULT_CHUNK_SIZE = 5 * 1024;
+
+ // The formatter used to format the size displayed while downloading.
+ private static final DecimalFormat SIZE_FORMAT = new DecimalFormat("#,##0.##"); //$NON-NLS-1$
+
+ // The singleton instance.
+ private static CacheManager instance;
+
+ /**
+ * Get the singleton cache manager.
+ *
+ * @return The singleton cache manager.
+ */
+ public static CacheManager getInstance() {
+ if (instance == null) {
+ instance = new CacheManager();
+ }
+ return instance;
+ }
+
+ /**
+ * Create a cache manager.
+ */
+ private CacheManager() {
+ }
+
+ /**
+ * Get the local path of a node's cached file.
+ * <p>
+ * The preferred location is within the plugin's state location, in
+ * example <code>&lt;state location&gt;agent_<hashcode_of_peerId>/remote/path/to/the/file...</code>.
+ * <p>
+ * If the plug-in is loaded in a RCP workspace-less environment, the
+ * fall back strategy is to use the users home directory.
+ *
+ * @param node
+ * The file/folder node.
+ * @return The local path of the node's cached file.
+ */
+ public IPath getCachePath(FSTreeNode node) {
+ File location = getCacheRoot();
+ String agentId = node.peerNode.getPeer().getID();
+ // Use Math.abs to avoid negative hash value.
+ String agent = WS_AGENT_DIR_PREFIX + Math.abs(agentId.hashCode());
+ IPath agentDir = new Path(location.getAbsolutePath()).append(agent);
+ File agentDirFile = agentDir.toFile();
+ if (!agentDirFile.exists()) {
+ agentDirFile.mkdir();
+ }
+ return appendNodePath(agentDir, node);
+ }
+
+ /**
+ * Get the local file of the specified node.
+ *
+ * <p>
+ * The preferred location is within the plugin's state location, in
+ * example <code>&lt;state location&gt;agent_<hashcode_of_peerId>/remote/path/to/the/file...</code>.
+ * <p>
+ * If the plug-in is loaded in a RCP workspace-less environment, the
+ * fall back strategy is to use the users home directory.
+ *
+ * @param node
+ * The file/folder node.
+ * @return The file object of the node's local cache.
+ */
+ public File getCacheFile(FSTreeNode node){
+ return getCachePath(node).toFile();
+ }
+
+ /**
+ * Get the cache file system's root directory on the local host's
+ * file system.
+ *
+ * @return The root folder's location of the cache file system.
+ */
+ public File getCacheRoot() {
+ File location;
+ try {
+ location = UIPlugin.getDefault().getStateLocation().toFile();
+ }catch (IllegalStateException e) {
+ // An RCP workspace-less environment (-data @none)
+ location = new File(System.getProperty("user.home"), ".tcf"); //$NON-NLS-1$ //$NON-NLS-2$
+ location = new File(location, "fs"); //$NON-NLS-1$
+ }
+
+ // Create the location if it not exist
+ if (!location.exists()) location.mkdir();
+ return location;
+ }
+
+ /**
+ * Append the path with the specified node's context path.
+ *
+ * @param path
+ * The path to be appended.
+ * @param node
+ * The file/folder node.
+ * @return The path to the node.
+ */
+ private IPath appendNodePath(IPath path, FSTreeNode node) {
+ if (!node.isRoot() && node.parent!=null) {
+ path = appendNodePath(path, node.parent);
+ return appendPathSegment(node, path, node.name);
+ }
+ if (node.isWindowsNode()) {
+ String name = node.name.substring(0, 1);
+ return appendPathSegment(node, path, name);
+ }
+ return path;
+ }
+
+ /**
+ * Append the path with the segment "name". Create a directory
+ * if the node is a directory which does not yet exist.
+ *
+ * @param node The file/folder node.
+ * @param path The path to appended.
+ * @param name The segment's name.
+ * @return The path with the segment "name" appended.
+ */
+ private IPath appendPathSegment(FSTreeNode node, IPath path, String name) {
+ IPath newPath = path.append(name);
+ File newFile = newPath.toFile();
+ if (node.isDirectory() && !newFile.exists()) {
+ newFile.mkdir();
+ }
+ return newPath;
+ }
+
+ /**
+ * Download the data of the file from the remote file system.
+ * Must be called within a UI thread.
+ * @param node
+ * The file node.
+ *
+ * @return true if it is successful, false there're errors or it is
+ * canceled.
+ */
+ public boolean download(final FSTreeNode node) {
+ IRunnableWithProgress runnable = new IRunnableWithProgress() {
+ @Override
+ public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
+ monitor.beginTask(NLS.bind(Messages.CacheManager_DowloadingFile, node.name), 100);
+ OutputStream output = null;
+ try {
+ // Write the data to its local cache file.
+ File file = getCachePath(node).toFile();
+ if(file.exists() && !file.canWrite()){
+ // If the file exists and is read-only, delete it.
+ file.delete();
+ }
+ output = new BufferedOutputStream(new FileOutputStream(file));
+ download2OutputStream(node, output, monitor);
+ if (monitor.isCanceled())
+ throw new InterruptedException();
+ } catch (IOException e) {
+ throw new InvocationTargetException(e);
+ } finally {
+ if (output != null) {
+ try {
+ output.close();
+ } catch (Exception e) {
+ }
+ }
+ monitor.done();
+ }
+ }
+ };
+ Shell parent = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
+ TimeTriggeredProgressMonitorDialog dialog = new TimeTriggeredProgressMonitorDialog(
+ parent, 250);
+ dialog.setCancelable(true);
+ File file = getCachePath(node).toFile();
+ try {
+ dialog.run(true, true, runnable);
+ // If downloading is successful, update the attributes of the file and
+ // set the last modified time to that of its corresponding file.
+ StateManager.getInstance().updateState(node);
+ // If the node is read-only, make the cache file read-only.
+ if(!node.isWritable())
+ file.setReadOnly();
+ return true;
+ } catch(TCFException e) {
+ MessageDialog.openError(parent, Messages.StateManager_UpdateFailureTitle, e.getLocalizedMessage());
+ } catch (InvocationTargetException e) {
+ // Something's gone wrong. Roll back the downloading and display the
+ // error.
+ file.delete();
+ PersistenceManager.getInstance().removeBaseTimestamp(node.getLocationURL());
+ displayError(parent, e);
+ } catch (InterruptedException e) {
+ // It is canceled. Just roll back the downloading result.
+ file.delete();
+ PersistenceManager.getInstance().removeBaseTimestamp(node.getLocationURL());
+ }
+ return false;
+ }
+
+ /**
+ * Upload the local files to the remote file system.
+ * Must be called within UI thread.
+ * @param nodes
+ * The files' location. Not null.
+ *
+ * @return true if it is successful, false there're errors or it is
+ * canceled.
+ */
+ public boolean upload(final FSTreeNode... nodes) {
+ Shell parent = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
+ try {
+ IRunnableWithProgress runnable = new IRunnableWithProgress() {
+ @Override
+ public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
+ try {
+ String message;
+ if(nodes.length==1)
+ message = NLS.bind(Messages.CacheManager_UploadSingleFile, nodes[0].name);
+ else
+ message = NLS.bind(Messages.CacheManager_UploadNFiles, Long.valueOf(nodes.length));
+ monitor.beginTask(message, 100);
+ boolean canceled = uploadFiles(monitor, nodes);
+ if (canceled)
+ throw new InterruptedException();
+ } catch (Exception e) {
+ throw new InvocationTargetException(e);
+ } finally {
+ monitor.done();
+ }
+ }
+ };
+ TimeTriggeredProgressMonitorDialog dialog = new TimeTriggeredProgressMonitorDialog(parent, 250);
+ dialog.setCancelable(true);
+ dialog.run(true, true, runnable);
+ return true;
+ } catch (InvocationTargetException e) {
+ // Something's gone wrong. Roll back the downloading and display the
+ // error.
+ displayError(parent, e);
+ } catch (InterruptedException e) {
+ // It is canceled. Just roll back the downloading result.
+ }
+ return false;
+ }
+
+ /**
+ * Display the error in an error dialog.
+ *
+ * @param node
+ * the file node.
+ * @param parent
+ * the parent shell.
+ * @param e
+ * The error exception.
+ */
+ private void displayError(Shell parent, InvocationTargetException e) {
+ Throwable target = e.getTargetException();
+ Throwable cause = target.getCause() != null ? target.getCause() : target;
+ MessageDialog.openError(parent, Messages.CacheManager_DownloadingError, cause.getLocalizedMessage());
+ }
+
+ /**
+ * Upload the specified files using the monitor to report the progress.
+ *
+ * @param peers
+ * The local files' peer files.
+ * @param locals
+ * The local files to be uploaded.
+ * @param monitor
+ * The monitor used to report the progress.
+ * @return true if it is canceled or else false.
+ * @throws Exception
+ * an Exception thrown during downloading and storing data.
+ */
+ public boolean uploadFiles(IProgressMonitor monitor, FSTreeNode... nodes) throws IOException {
+ BufferedInputStream input = null;
+ BufferedOutputStream output = null;
+ // The buffer used to download the file.
+ byte[] data = new byte[DEFAULT_CHUNK_SIZE];
+ // Calculate the total size.
+ long totalSize = 0;
+ for (FSTreeNode node : nodes) {
+ File file = getCachePath(node).toFile();
+ totalSize += file.length();
+ }
+ // Calculate the chunk size of one percent.
+ int chunk_size = (int) totalSize / 100;
+ // The current reading percentage.
+ int percentRead = 0;
+ // The current length of read bytes.
+ long bytesRead = 0;
+ for (int i = 0; i < nodes.length && !monitor.isCanceled(); i++) {
+ File file = getCachePath(nodes[i]).toFile();
+ try {
+ URL url = nodes[i].getLocationURL();
+ TcfURLConnection connection = (TcfURLConnection) url.openConnection();
+ connection.setDoInput(false);
+ connection.setDoOutput(true);
+ input = new BufferedInputStream(new FileInputStream(file));
+ output = new BufferedOutputStream(connection.getOutputStream());
+
+ // Total size displayed on the progress dialog.
+ String fileLength = formatSize(file.length());
+ int length;
+ while ((length = input.read(data)) >= 0 && !monitor.isCanceled()) {
+ output.write(data, 0, length);
+ output.flush();
+ bytesRead += length;
+ if (chunk_size != 0) {
+ int percent = (int) bytesRead / chunk_size;
+ if (percent != percentRead) { // Update the progress.
+ monitor.worked(percent - percentRead);
+ percentRead = percent; // Remember the percentage.
+ // Report the progress.
+ monitor.subTask(NLS.bind(Messages.CacheManager_UploadingProgress, new Object[]{file.getName(), formatSize(bytesRead), fileLength}));
+ }
+ }
+ }
+ } finally {
+ if (output != null) {
+ try {
+ output.close();
+ } catch (Exception e) {
+ }
+ }
+ if (input != null) {
+ try {
+ input.close();
+ } catch (Exception e) {
+ }
+ }
+ if(!monitor.isCanceled()){
+ // Once upload is successful, synchronize the modified time.
+ try {
+ StateManager.getInstance().commitState(nodes[i]);
+ } catch (TCFException tcfe) {
+ throw new IOException(tcfe.getLocalizedMessage());
+ }
+ }
+ }
+ }
+ return monitor.isCanceled();
+ }
+
+ /**
+ * Download the specified file into an output stream using the monitor to report the progress.
+ *
+ * @param node
+ * The file to be downloaded.
+ * @param output
+ * The output stream.
+ * @param monitor
+ * The monitor used to report the progress.
+ * @throws IOException
+ * an IOException thrown during downloading and storing data.
+ */
+ public void download2OutputStream(FSTreeNode node, OutputStream output, IProgressMonitor monitor) throws IOException {
+ InputStream input = null;
+ // Open the input stream of the node using the tcf stream protocol.
+ try{
+ URL url = node.getLocationURL();
+ InputStream in = url.openStream();
+ input = new BufferedInputStream(in);
+ // The buffer used to download the file.
+ byte[] data = new byte[DEFAULT_CHUNK_SIZE];
+ // Calculate the chunk size of one percent.
+ int chunk_size = (int) node.attr.size / 100;
+ // Total size displayed on the progress dialog.
+ String total_size = formatSize(node.attr.size);
+
+ int percentRead = 0;
+ long bytesRead = 0;
+ int length;
+ while ((length = input.read(data)) >= 0 && !monitor.isCanceled()) {
+ output.write(data, 0, length);
+ output.flush();
+ bytesRead += length;
+ if (chunk_size != 0) {
+ int percent = (int) bytesRead / chunk_size;
+ if (percent != percentRead) { // Update the progress.
+ monitor.worked(percent - percentRead);
+ percentRead = percent; // Remember the percentage.
+ // Report the progress.
+ monitor.subTask(NLS.bind(Messages.CacheManager_DownloadingProgress, formatSize(bytesRead), total_size));
+ }
+ }
+ }
+ }finally{
+ if (input != null) {
+ try {
+ input.close();
+ } catch (Exception e) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Use the SIZE_FORMAT to format the file's size. The rule is: 1. If the
+ * size is less than 1024 bytes, then show it as "####" bytes. 2. If the
+ * size is less than 1024 KBs, while more than 1 KB, then show it as
+ * "####.##" KBs. 3. If the size is more than 1 MB, then show it as
+ * "####.##" MBs.
+ *
+ * @param size
+ * The file size to be displayed.
+ * @return The string representation of the size.
+ */
+ private String formatSize(long size) {
+ double kbSize = size / 1024.0;
+ if (kbSize < 1.0) {
+ return SIZE_FORMAT.format(size) + Messages.CacheManager_Bytes;
+ }
+ double mbSize = kbSize / 1024.0;
+ if (mbSize < 1.0)
+ return SIZE_FORMAT.format(kbSize) + Messages.CacheManager_KBs;
+ return SIZE_FORMAT.format(mbSize) + Messages.CacheManager_MBs;
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/CachePropertyTester.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/CachePropertyTester.java
new file mode 100644
index 000000000..bb4972146
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/CachePropertyTester.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ * William Chen (Wind River)- [345552] Edit the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.handlers;
+
+import org.eclipse.core.expressions.PropertyTester;
+
+/**
+ * Provide a tester to test if the current auto saving mode is on or off.
+ *
+ */
+public class CachePropertyTester extends PropertyTester {
+ /**
+ * Create a cache property tester.
+ */
+ public CachePropertyTester() {
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.expressions.IPropertyTester#test(java.lang.Object, java.lang.String, java.lang.Object[], java.lang.Object)
+ */
+ @Override
+ public boolean test(Object receiver, String property, Object[] args, Object expectedValue) {
+ if(property.equals("isAutoSavingOn")){ //$NON-NLS-1$
+ return PersistenceManager.getInstance().isAutoSaving();
+ }
+ return false;
+ }
+
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/CommitHandler.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/CommitHandler.java
new file mode 100644
index 000000000..3e6bb56d9
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/CommitHandler.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ * William Chen (Wind River)- [345552] Edit the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.handlers;
+
+import java.io.File;
+
+import org.eclipse.compare.CompareUI;
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.tcf.te.tcf.filesystem.internal.compare.LocalTypedElement;
+import org.eclipse.tcf.te.tcf.filesystem.internal.compare.MergeEditorInput;
+import org.eclipse.tcf.te.tcf.filesystem.internal.compare.RemoteTypedElement;
+import org.eclipse.tcf.te.tcf.filesystem.internal.exceptions.TCFException;
+import org.eclipse.tcf.te.tcf.filesystem.internal.nls.Messages;
+import org.eclipse.tcf.te.tcf.filesystem.model.CacheState;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSTreeNode;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+/**
+ * The handler that commits the content of a modified file to the target file system.
+ */
+public class CommitHandler extends AbstractHandler {
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.commands.AbstractHandler#execute(org.eclipse.core.commands.ExecutionEvent)
+ */
+ @Override
+ public Object execute(ExecutionEvent event) throws ExecutionException {
+ IStructuredSelection selection = (IStructuredSelection) HandlerUtil.getCurrentSelectionChecked(event);
+ FSTreeNode node = (FSTreeNode) selection.getFirstElement();
+ try {
+ // Refresh the node to get the latest state.
+ StateManager.getInstance().refreshState(node);
+ Shell parent = HandlerUtil.getActiveShell(event);
+ File file = CacheManager.getInstance().getCacheFile(node);
+ if (file.exists()) {
+ CacheState state = StateManager.getInstance().getCacheState(node);
+ switch (state) {
+ case conflict:
+ String title = Messages.CmmitHandler_StateChangedDialogTitle;
+ String message = NLS.bind(Messages.CmmitHandler_StateChangedMessage, node.name);
+ MessageDialog msgDialog = new MessageDialog(parent, title, null, message, MessageDialog.QUESTION, new String[] { Messages.CmmitHandler_Merge, Messages.CmmitHandler_CommitAnyway,
+ Messages.CmmitHandler_Cancel }, 0);
+ int index = msgDialog.open();
+ if (index == 0) {// Merge
+ LocalTypedElement local = new LocalTypedElement(node);
+ RemoteTypedElement remote = new RemoteTypedElement(node);
+ IWorkbenchPage page = HandlerUtil.getActiveSite(event).getPage();
+ MergeEditorInput input = new MergeEditorInput(local, remote, page);
+ CompareUI.openCompareDialog(input);
+ } else if (index == 1) { // Commit anyway
+ CacheManager.getInstance().upload(node);
+ }
+ break;
+ case modified:
+ // Commit it normally.
+ CacheManager.getInstance().upload(node);
+ break;
+ case consistent:
+ break;
+ case outdated:
+ break;
+ }
+ } else {
+ String message = NLS.bind(Messages.CmmitHandler_FileDeleted, file.getName());
+ MessageDialog.openError(parent, Messages.CmmitHandler_ErrorTitle, message);
+ }
+ } catch (TCFException e) {
+ Shell parent = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
+ MessageDialog.openError(parent, Messages.StateManager_RefreshFailureTitle, e.getLocalizedMessage());
+ }
+ return null;
+ }
+
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/ContentTypeHelper.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/ContentTypeHelper.java
new file mode 100644
index 000000000..d2b55c94e
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/ContentTypeHelper.java
@@ -0,0 +1,134 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * William Chen (Wind River)- [345387]Open the remote files with a proper editor
+ * William Chen (Wind River) [360494]Provide an "Open With" action in the pop
+ * up menu of file system nodes of Target Explorer.
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.handlers;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import org.eclipse.core.filesystem.EFS;
+import org.eclipse.core.filesystem.IFileStore;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.content.IContentType;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSTreeNode;
+
+/**
+ * The content type helper used to provide helping methods about the content
+ * types of the files in the remote file system.
+ */
+public class ContentTypeHelper {
+ // The binary content type's id.
+ private static final String CONTENT_TYPE_BINARY_ID = "org.eclipse.cdt.core.binaryFile"; //$NON-NLS-1$
+ // The singleton of the content type helper.
+ private static ContentTypeHelper instance;
+
+ /**
+ * Get the singleton instance of the content type helper.
+ *
+ * @return The singleton instance of the content type helper.
+ */
+ public static ContentTypeHelper getInstance() {
+ if (instance == null) {
+ instance = new ContentTypeHelper();
+ }
+ return instance;
+ }
+
+ /**
+ * Judges if the node is a binary file.
+ *
+ * @param node
+ * The file node.
+ * @return true if the node is a binary file or else false.
+ */
+ public boolean isBinaryFile(FSTreeNode node) {
+ IContentType contentType = getContentType(node);
+ if (contentType != null) {
+ IContentType binaryFile = Platform.getContentTypeManager()
+ .getContentType(CONTENT_TYPE_BINARY_ID);
+ if (binaryFile != null && contentType.isKindOf(binaryFile))
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get the content type of the specified file node.
+ *
+ * @param node
+ * The file node.
+ * @return The content type of the file node.
+ */
+ public IContentType getContentType(FSTreeNode node) {
+ if (PersistenceManager.getInstance().isUnresovled(node))
+ // If it is already known unresolvable.
+ return null;
+ IContentType contentType = PersistenceManager.getInstance().getResolved(node);
+ if (contentType != null)
+ // If it is already known to have a certain content type.
+ return contentType;
+ // First check the content type by its name.
+ contentType = Platform.getContentTypeManager().findContentTypeFor(
+ node.name);
+ if (contentType == null) { // Then find the content type by its stream.
+ try {
+ contentType = findContentTypeByStream(node);
+ } catch (Exception e) {
+ }
+ }
+ if (contentType != null) { // If it is resolved, cache it.
+ PersistenceManager.getInstance().addResovled(node, contentType);
+ } else { // Or else, remember it as an unresolvable.
+ PersistenceManager.getInstance().addUnresolved(node);
+ }
+ return contentType;
+ }
+
+ /**
+ * Find the content type of the file using its content stream.
+ *
+ * @param node
+ * The file node.
+ * @return The content type of the file.
+ * @throws CoreException
+ * If the path of its local cache file couldn't be found.
+ * @throws IOException
+ * If something goes wrong during the content type parsing.
+ */
+ private IContentType findContentTypeByStream(FSTreeNode node) throws CoreException, IOException {
+ InputStream is = null;
+ try {
+ File file = CacheManager.getInstance().getCacheFile(node);
+ if (file.exists()) {
+ // If the local cache file exits.
+ IPath path = CacheManager.getInstance().getCachePath(node);
+ IFileStore fileStore = EFS.getLocalFileSystem().getStore(path);
+ is = fileStore.openInputStream(EFS.NONE, null);
+ } else {
+ // Use its URL stream.
+ URL url = node.getLocationURL();
+ is = url.openStream();
+ }
+ return Platform.getContentTypeManager().findContentTypeFor(is, node.name);
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/FSTreeNodePropertyTester.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/FSTreeNodePropertyTester.java
new file mode 100644
index 000000000..1d8e5b2b5
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/FSTreeNodePropertyTester.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ * William Chen (Wind River) - [345387]Open the remote files with a proper editor
+ * William Chen (Wind River) - [352302]Opening a file in an editor depending on
+ * the client's permissions.
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.handlers;
+
+import java.io.File;
+
+import org.eclipse.core.expressions.PropertyTester;
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.tcf.te.tcf.filesystem.model.CacheState;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSTreeNode;
+
+/**
+ * The property tester of an FSTreeNode. The properties include "isFile"
+ * if it is a file node, "isDirectory" if it is a directory, "isBinaryFile"
+ * if it is a binary file, "isReadable" if it is readable, "isWritable" if
+ * it is writable, "isExecutable" if it is executable and "getCacheState" to
+ * get a node's state.
+ */
+public class FSTreeNodePropertyTester extends PropertyTester {
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.expressions.IPropertyTester#test(java.lang.Object, java.lang.String, java.lang.Object[], java.lang.Object)
+ */
+ @Override
+ public boolean test(Object receiver, String property, Object[] args, Object expectedValue) {
+ Assert.isTrue(receiver instanceof FSTreeNode);
+ FSTreeNode node = (FSTreeNode) receiver;
+ if (property.equals("isFile")) { //$NON-NLS-1$
+ return node.isFile();
+ } else if (property.equals("isDirectory")) { //$NON-NLS-1$
+ return node.isDirectory();
+ } else if (property.equals("isBinaryFile")) { //$NON-NLS-1$
+ return ContentTypeHelper.getInstance().isBinaryFile(node);
+ } else if (property.equals("isReadable")){ //$NON-NLS-1$
+ return node.isReadable();
+ } else if (property.equals("isWritable")){ //$NON-NLS-1$
+ return node.isWritable();
+ } else if (property.equals("isExecutable")){ //$NON-NLS-1$
+ return node.isExecutable();
+ } else if (property.equals("getCacheState")){ //$NON-NLS-1$
+ File file = CacheManager.getInstance().getCacheFile(node);
+ if(!file.exists())
+ return false;
+ CacheState state = StateManager.getInstance().getCacheState(node);
+ return state.name().equals(expectedValue);
+ }
+ return false;
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/MergeHandler.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/MergeHandler.java
new file mode 100644
index 000000000..4963ff6ae
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/MergeHandler.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ * William Chen (Wind River)- [345552] Edit the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.handlers;
+
+import org.eclipse.compare.CompareUI;
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.tcf.te.tcf.filesystem.internal.compare.LocalTypedElement;
+import org.eclipse.tcf.te.tcf.filesystem.internal.compare.MergeEditorInput;
+import org.eclipse.tcf.te.tcf.filesystem.internal.compare.RemoteTypedElement;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSTreeNode;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+/**
+ * The handler used to merge a file which is conflicting with its remote file.
+ */
+public class MergeHandler extends AbstractHandler {
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.commands.AbstractHandler#execute(org.eclipse.core.commands.ExecutionEvent)
+ */
+ @Override
+ public Object execute(ExecutionEvent event) throws ExecutionException {
+ IStructuredSelection selection = (IStructuredSelection) HandlerUtil.getCurrentSelectionChecked(event);
+ FSTreeNode node = (FSTreeNode) selection.getFirstElement();
+ LocalTypedElement local = new LocalTypedElement(node);
+ RemoteTypedElement remote = new RemoteTypedElement(node);
+ IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
+ MergeEditorInput input = new MergeEditorInput(local, remote, page);
+ CompareUI.openCompareDialog(input);
+ return null;
+ }
+
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/OpenFileHandler.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/OpenFileHandler.java
new file mode 100644
index 000000000..03106e122
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/OpenFileHandler.java
@@ -0,0 +1,163 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * William Chen (Wind River)- [345387]Open the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.handlers;
+
+import java.io.File;
+
+import org.eclipse.compare.CompareUI;
+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.IFileStore;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.tcf.te.tcf.filesystem.internal.compare.LocalTypedElement;
+import org.eclipse.tcf.te.tcf.filesystem.internal.compare.MergeEditorInput;
+import org.eclipse.tcf.te.tcf.filesystem.internal.compare.RemoteTypedElement;
+import org.eclipse.tcf.te.tcf.filesystem.internal.exceptions.TCFException;
+import org.eclipse.tcf.te.tcf.filesystem.internal.nls.Messages;
+import org.eclipse.tcf.te.tcf.filesystem.model.CacheState;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSTreeNode;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.handlers.HandlerUtil;
+import org.eclipse.ui.ide.FileStoreEditorInput;
+import org.eclipse.ui.ide.IDE;
+
+/**
+ * The action handler to open a file on the remote file system.
+ */
+public class OpenFileHandler extends AbstractHandler {
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.commands.AbstractHandler#execute(org.eclipse.core.commands.ExecutionEvent)
+ */
+ @Override
+ public Object execute(ExecutionEvent event) throws ExecutionException {
+ IStructuredSelection selection = (IStructuredSelection) HandlerUtil.getCurrentSelectionChecked(event);
+ final FSTreeNode node = (FSTreeNode) selection.getFirstElement();
+ IWorkbenchPage page = HandlerUtil.getActiveSite(event).getPage();
+ if (ContentTypeHelper.getInstance().isBinaryFile(node)) {
+ // If the file is a binary file.
+ Shell parent = HandlerUtil.getActiveShell(event);
+ MessageDialog.openWarning(parent, Messages.OpenFileHandler_Warning,
+ Messages.OpenFileHandler_OpeningBinaryNotSupported);
+ } else {
+ // Open the file node.
+ openFile(node, page);
+ }
+ return null;
+ }
+
+ /**
+ * Open the file node in an editor of the specified workbench page. If the
+ * local cache file of the node is stale, then download it. Then open its
+ * local cache file.
+ *
+ * @param node
+ * The file node to be opened.
+ * @param page
+ * The workbench page in which the editor is opened.
+ */
+ private void openFile(FSTreeNode node, IWorkbenchPage page) {
+ File file = CacheManager.getInstance().getCacheFile(node);
+ if (!file.exists()) {
+ // If the file node's local cache does not exist yet, download it.
+ boolean successful = CacheManager.getInstance().download(node);
+ if (!successful) {
+ return;
+ }
+ }
+ if (!PersistenceManager.getInstance().isAutoSaving()) {
+ openEditor(page, node);
+ } else {
+ try {
+ StateManager.getInstance().refreshState(node);
+ } catch (TCFException e) {
+ Shell parent = page.getWorkbenchWindow().getShell();
+ MessageDialog.openError(parent, Messages.StateManager_RefreshFailureTitle, e.getLocalizedMessage());
+ return;
+ }
+ CacheState state = StateManager.getInstance().getCacheState(node);
+ switch (state) {
+ case consistent:
+ openEditor(page, node);
+ break;
+ case modified: {
+ // If the file node's local cache has been modified, upload it
+ // before open it.
+ boolean successful = CacheManager.getInstance().upload(node);
+ if (successful)
+ openEditor(page, node);
+ }
+ break;
+ case outdated: {
+ // If the file node's local cache does not exist yet, download
+ // it.
+ boolean successful = CacheManager.getInstance().download(node);
+ if (successful)
+ openEditor(page, node);
+ }
+ break;
+ case conflict: {
+ String title = Messages.OpenFileHandler_ConflictingTitle;
+ String message = NLS.bind(Messages.OpenFileHandler_ConflictingMessage, node.name);
+ Shell parent = page.getWorkbenchWindow().getShell();
+ MessageDialog msgDialog = new MessageDialog(parent, title, null, message, MessageDialog.QUESTION, new String[] { Messages.OpenFileHandler_Merge, Messages.OpenFileHandler_OpenAnyway,
+ Messages.OpenFileHandler_Cancel }, 0);
+ int index = msgDialog.open();
+ if (index == 0) {
+ LocalTypedElement local = new LocalTypedElement(node);
+ RemoteTypedElement remote = new RemoteTypedElement(node);
+ MergeEditorInput input = new MergeEditorInput(local, remote, page);
+ CompareUI.openCompareDialog(input);
+ } else if (index == 1) {
+ openEditor(page, node);
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ /**
+ * Open the editor to display the file node in the UI thread.
+ *
+ * @param page
+ * The workbench page in which the editor is opened.
+ * @param node
+ * The file node whose local cache file is opened.
+ */
+ private void openEditor(final IWorkbenchPage page, final FSTreeNode node) {
+ Display display = page.getWorkbenchWindow().getWorkbench().getDisplay();
+ display.asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ IPath path = CacheManager.getInstance().getCachePath(node);
+ IFileStore fileStore = EFS.getLocalFileSystem().getStore(path);
+ String editorID = PersistenceManager.getInstance().getPersistentProperties(node).get(IDE.EDITOR_KEY);
+ try {
+ if(editorID!=null){
+ FileStoreEditorInput input = new FileStoreEditorInput(fileStore);
+ page.openEditor(input, editorID, true, IWorkbenchPage.MATCH_INPUT|IWorkbenchPage.MATCH_ID);
+ }else{
+ IDE.openEditorOnFileStore(page, fileStore);
+ }
+ } catch (PartInitException e) {
+ }
+ }
+ });
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/OpenWithContribution.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/OpenWithContribution.java
new file mode 100644
index 000000000..92c7f5b81
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/OpenWithContribution.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * William Chen (Wind River) [360494]Provide an "Open With" action in the pop
+ * up menu of file system nodes of Target Explorer.
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.handlers;
+
+import org.eclipse.core.expressions.IEvaluationContext;
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.jface.action.IContributionItem;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSTreeNode;
+import org.eclipse.ui.ISources;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.actions.CompoundContributionItem;
+import org.eclipse.ui.handlers.IHandlerService;
+import org.eclipse.ui.menus.IWorkbenchContribution;
+import org.eclipse.ui.services.IServiceLocator;
+
+/**
+ * The dynamic contribution of "Open With" submenu items.
+ */
+public class OpenWithContribution extends CompoundContributionItem implements IWorkbenchContribution {
+ // Service locator to located the handler service.
+ private IServiceLocator serviceLocator;
+
+ /**
+ * Create the contribution instance.
+ */
+ public OpenWithContribution() {
+ }
+
+ /**
+ * Create the contribution instance with the specified id.
+ */
+ public OpenWithContribution(String id) {
+ super(id);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see
+ * org.eclipse.ui.menus.IWorkbenchContribution#initialize(org.eclipse.ui.services.IServiceLocator
+ * )
+ */
+ @Override
+ public void initialize(IServiceLocator serviceLocator) {
+ this.serviceLocator = serviceLocator;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.ui.actions.CompoundContributionItem#getContributionItems()
+ */
+ @Override
+ protected IContributionItem[] getContributionItems() {
+ // Get the selected node.
+ IHandlerService service = (IHandlerService) this.serviceLocator
+ .getService(IHandlerService.class);
+ IEvaluationContext state = service.getCurrentState();
+ ISelection selection = (ISelection) state
+ .getVariable(ISources.ACTIVE_CURRENT_SELECTION_NAME);
+ IStructuredSelection iss = (IStructuredSelection) selection;
+ Object obj = iss.getFirstElement();
+ Assert.isTrue(obj instanceof FSTreeNode);
+ FSTreeNode node = (FSTreeNode) obj;
+ IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
+ return new IContributionItem[] { new OpenWithMenu(page, node) };
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/OpenWithMenu.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/OpenWithMenu.java
new file mode 100644
index 000000000..d64a99faf
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/OpenWithMenu.java
@@ -0,0 +1,464 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * William Chen (Wind River) [360494]Provide an "Open With" action in the pop
+ * up menu of file system nodes of Target Explorer.
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.handlers;
+
+import java.io.File;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Map;
+
+import org.eclipse.compare.CompareUI;
+import org.eclipse.core.filesystem.EFS;
+import org.eclipse.core.filesystem.IFileStore;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.QualifiedName;
+import org.eclipse.core.runtime.content.IContentType;
+import org.eclipse.jface.action.ContributionItem;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.window.Window;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.MenuItem;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.tcf.te.tcf.filesystem.activator.UIPlugin;
+import org.eclipse.tcf.te.tcf.filesystem.internal.compare.LocalTypedElement;
+import org.eclipse.tcf.te.tcf.filesystem.internal.compare.MergeEditorInput;
+import org.eclipse.tcf.te.tcf.filesystem.internal.compare.RemoteTypedElement;
+import org.eclipse.tcf.te.tcf.filesystem.internal.exceptions.TCFException;
+import org.eclipse.tcf.te.tcf.filesystem.internal.nls.Messages;
+import org.eclipse.tcf.te.tcf.filesystem.model.CacheState;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSTreeNode;
+import org.eclipse.ui.IEditorDescriptor;
+import org.eclipse.ui.IEditorRegistry;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.dialogs.EditorSelectionDialog;
+import org.eclipse.ui.ide.FileStoreEditorInput;
+import org.eclipse.ui.ide.IDE;
+
+/**
+ * A menu for opening files in the target explorer.
+ * <p>
+ * An <code>OpenWithMenu</code> is used to populate a menu with "Open With" actions. One action is
+ * added for each editor which is applicable to the selected file. If the user selects one of these
+ * items, the corresponding editor is opened on the file.
+ * </p>
+ *
+ * @since 3.7 - Copied and modified based on org.eclipse.ui.actions.OpenWithMenu to avoid
+ * introducing org.eclipse.core.resources
+ */
+public class OpenWithMenu extends ContributionItem {
+ private static final String DEFAULT_TEXT_EDITOR = "org.eclipse.ui.DefaultTextEditor"; //$NON-NLS-1$
+
+ /**
+ * The id of this action.
+ */
+ public static final String ID = UIPlugin.getUniqueIdentifier() + ".OpenWithMenu";//$NON-NLS-1$
+
+ /*
+ * Compares the labels from two IEditorDescriptor objects
+ */
+ private static final Comparator<IEditorDescriptor> comparer = new Comparator<IEditorDescriptor>() {
+ private Collator collator = Collator.getInstance();
+
+ @Override
+ public int compare(IEditorDescriptor arg0, IEditorDescriptor arg1) {
+ String s1 = arg0.getLabel();
+ String s2 = arg1.getLabel();
+ return collator.compare(s1, s2);
+ }
+ };
+ // The selected tree node.
+ FSTreeNode node;
+ // The current workbench page.
+ IWorkbenchPage page;
+ // The editor registry.
+ IEditorRegistry registry;
+
+ /**
+ * Create an instance using the specified page and the specified FSTreeNode.
+ *
+ * @param page The page to open the editor.
+ * @param node The FSTreeNode to be opened.
+ */
+ public OpenWithMenu(IWorkbenchPage page, FSTreeNode node) {
+ super(ID);
+ this.node = node;
+ this.page = page;
+ this.registry = PlatformUI.getWorkbench().getEditorRegistry();
+ }
+
+ /**
+ * Returns an image to show for the corresponding editor descriptor.
+ *
+ * @param editorDesc the editor descriptor, or null for the system editor
+ * @return the image or null
+ */
+ private Image getImage(IEditorDescriptor editorDesc) {
+ ImageDescriptor imageDesc = getImageDescriptor(editorDesc);
+ if (imageDesc == null) {
+ return null;
+ }
+ return imageDesc.createImage();
+ }
+
+ /**
+ * Returns the image descriptor for the given editor descriptor, or null if it has no image.
+ */
+ private ImageDescriptor getImageDescriptor(IEditorDescriptor editorDesc) {
+ ImageDescriptor imageDesc = null;
+ if (editorDesc == null) {
+ imageDesc = registry.getImageDescriptor(node.name);
+ // TODO: is this case valid, and if so, what are the implications for content-type
+ // editor bindings?
+ }
+ else {
+ imageDesc = editorDesc.getImageDescriptor();
+ }
+ if (imageDesc == null) {
+ if (editorDesc.getId().equals(IEditorRegistry.SYSTEM_EXTERNAL_EDITOR_ID)) {
+ imageDesc = registry.getSystemExternalEditorImageDescriptor(node.name);
+ }
+ }
+ return imageDesc;
+ }
+
+ /**
+ * Creates the menu item for the editor descriptor.
+ *
+ * @param menu the menu to add the item to
+ * @param descriptor the editor descriptor, or null for the system editor
+ * @param preferredEditor the descriptor of the preferred editor, or <code>null</code>
+ */
+ private void createMenuItem(Menu menu, final IEditorDescriptor descriptor, final IEditorDescriptor preferredEditor) {
+ final MenuItem menuItem = new MenuItem(menu, SWT.RADIO);
+ boolean isPreferred = preferredEditor != null && descriptor.getId()
+ .equals(preferredEditor.getId());
+ menuItem.setSelection(isPreferred);
+ menuItem.setText(descriptor.getLabel());
+ Image image = getImage(descriptor);
+ if (image != null) {
+ menuItem.setImage(image);
+ }
+ Listener listener = new Listener() {
+ @Override
+ public void handleEvent(Event event) {
+ switch (event.type) {
+ case SWT.Selection:
+ if (menuItem.getSelection()) {
+ syncOpen(descriptor, false);
+ }
+ break;
+ }
+ }
+ };
+ menuItem.addListener(SWT.Selection, listener);
+ }
+
+ /**
+ * Creates the Other... menu item
+ *
+ * @param menu the menu to add the item to
+ */
+ @SuppressWarnings("unused")
+ private void createOtherMenuItem(final Menu menu) {
+ new MenuItem(menu, SWT.SEPARATOR);
+ final MenuItem menuItem = new MenuItem(menu, SWT.PUSH);
+ menuItem.setText(Messages.OpenWithMenu_OpenWith);
+ Listener listener = new Listener() {
+ @Override
+ public void handleEvent(Event event) {
+ switch (event.type) {
+ case SWT.Selection:
+ EditorSelectionDialog dialog = new EditorSelectionDialog(menu.getShell());
+ dialog.setMessage(NLS
+ .bind(Messages.OpenWithMenu_ChooseEditorForOpening, node.name));
+ if (dialog.open() == Window.OK) {
+ IEditorDescriptor editor = dialog.getSelectedEditor();
+ if (editor != null) {
+ syncOpen(editor, editor.isOpenExternal());
+ }
+ }
+ break;
+ }
+ }
+ };
+ menuItem.addListener(SWT.Selection, listener);
+ }
+
+ /**
+ * Get the default editor for this FSTreeNode.
+ *
+ * @return The descriptor of the default editor.
+ */
+ private IEditorDescriptor getDefaultEditor() {
+ // Try file specific editor.
+ try {
+ String editorID = PersistenceManager.getInstance().getPersistentProperties(node)
+ .get(IDE.EDITOR_KEY);
+ if (editorID != null) {
+ IEditorDescriptor desc = registry.findEditor(editorID);
+ if (desc != null) {
+ return desc;
+ }
+ }
+ }
+ catch (Exception e) {
+ // do nothing
+ }
+
+ IContentType contentType = null;
+ contentType = ContentTypeHelper.getInstance().getContentType(node);
+ // Try lookup with filename
+ return registry.getDefaultEditor(node.name, contentType);
+ }
+
+ /*
+ * (non-Javadoc) Fills the menu with perspective items.
+ */
+ @SuppressWarnings("unused")
+ @Override
+ public void fill(Menu menu, int index) {
+
+ IEditorDescriptor defaultEditor = registry.findEditor(DEFAULT_TEXT_EDITOR);
+ IEditorDescriptor preferredEditor = getDefaultEditor();
+
+ IEditorDescriptor[] editors = registry.getEditors(node.name, ContentTypeHelper
+ .getInstance().getContentType(node));
+ Collections.sort(Arrays.asList(editors), comparer);
+
+ boolean defaultFound = false;
+
+ // Check that we don't add it twice. This is possible
+ // if the same editor goes to two mappings.
+ ArrayList<IEditorDescriptor> alreadyMapped = new ArrayList<IEditorDescriptor>();
+
+ for (int i = 0; i < editors.length; i++) {
+ IEditorDescriptor editor = editors[i];
+ if (!alreadyMapped.contains(editor)) {
+ createMenuItem(menu, editor, preferredEditor);
+ if (defaultEditor != null && editor.getId().equals(defaultEditor.getId())) {
+ defaultFound = true;
+ }
+ alreadyMapped.add(editor);
+ }
+ }
+
+ // Only add a separator if there is something to separate
+ if (editors.length > 0) {
+ new MenuItem(menu, SWT.SEPARATOR);
+ }
+
+ // Add default editor. Check it if it is saved as the preference.
+ if (!defaultFound && defaultEditor != null) {
+ createMenuItem(menu, defaultEditor, preferredEditor);
+ }
+
+ // Add system editor (should never be null)
+ IEditorDescriptor descriptor = registry
+ .findEditor(IEditorRegistry.SYSTEM_EXTERNAL_EDITOR_ID);
+ createMenuItem(menu, descriptor, preferredEditor);
+
+ createDefaultMenuItem(menu);
+
+ // add Other... menu item
+ createOtherMenuItem(menu);
+ }
+
+ /*
+ * (non-Javadoc) Returns whether this menu is dynamic.
+ */
+ @Override
+ public boolean isDynamic() {
+ return true;
+ }
+
+ /**
+ * Creates the menu item for clearing the current selection.
+ *
+ * @param menu the menu to add the item to
+ * @param file the file being edited
+ */
+ private void createDefaultMenuItem(Menu menu) {
+ final MenuItem menuItem = new MenuItem(menu, SWT.RADIO);
+ menuItem.setSelection(getDefaultEditor() == null);
+ menuItem.setText(Messages.OpenWithMenu_DefaultEditor);
+
+ Listener listener = new Listener() {
+ @Override
+ public void handleEvent(Event event) {
+ switch (event.type) {
+ case SWT.Selection:
+ if (menuItem.getSelection()) {
+ PersistenceManager.getInstance().getPersistentProperties(node)
+ .put(IDE.EDITOR_KEY, null);
+ try {
+ syncOpen(getEditorDescriptor(), false);
+ }
+ catch (PartInitException e) {
+ e.printStackTrace();
+ }
+ }
+ break;
+ }
+ }
+ };
+
+ menuItem.addListener(SWT.Selection, listener);
+ }
+
+ /**
+ * Get an appropriate editor for the FSTreeNode. If the default editor is not found, it will
+ * search the in-place editor, the external editor and finally the default text editor.
+ *
+ * @return An appropriate editor to open the node using "Default Editor".
+ * @throws PartInitException
+ */
+ protected IEditorDescriptor getEditorDescriptor() throws PartInitException {
+ IEditorDescriptor defaultDescriptor = getDefaultEditor();
+ if (defaultDescriptor != null) {
+ return defaultDescriptor;
+ }
+
+ IEditorDescriptor editorDesc = defaultDescriptor;
+
+ // next check the OS for in-place editor (OLE on Win32)
+ if (registry.isSystemInPlaceEditorAvailable(node.name)) {
+ editorDesc = registry.findEditor(IEditorRegistry.SYSTEM_INPLACE_EDITOR_ID);
+ }
+
+ // next check with the OS for an external editor
+ if (editorDesc == null && registry.isSystemExternalEditorAvailable(node.name)) {
+ editorDesc = registry.findEditor(IEditorRegistry.SYSTEM_EXTERNAL_EDITOR_ID);
+ }
+
+ // next lookup the default text editor
+ if (editorDesc == null) {
+ editorDesc = registry.findEditor(DEFAULT_TEXT_EDITOR);
+ }
+
+ // if no valid editor found, bail out
+ if (editorDesc == null) {
+ throw new PartInitException(Messages.OpenWithMenu_NoEditorFound);
+ }
+
+ return editorDesc;
+ }
+
+ /**
+ * Synchronize and open the file using the specified editor descriptor. If openUsingDescriptor
+ * is true, it will try to use an external editor to open it if an eclipse editor is not
+ * available.
+ *
+ * @param editorDescriptor The editor descriptor used to open the node.
+ * @param openUsingDescriptor If an external editor should be used to open the node.
+ */
+ protected void syncOpen(IEditorDescriptor editorDescriptor, boolean openUsingDescriptor) {
+ File file = CacheManager.getInstance().getCacheFile(node);
+ if (!file.exists()) {
+ // If the file node's local cache does not exist yet, download it.
+ boolean successful = CacheManager.getInstance().download(node);
+ if (!successful) {
+ return;
+ }
+ }
+ if (!PersistenceManager.getInstance().isAutoSaving()) {
+ openInEditor(editorDescriptor, openUsingDescriptor);
+ }
+ else {
+ try {
+ StateManager.getInstance().refreshState(node);
+ }
+ catch (TCFException e) {
+ Shell parent = page.getWorkbenchWindow().getShell();
+ MessageDialog.openError(parent, Messages.StateManager_RefreshFailureTitle, e
+ .getLocalizedMessage());
+ return;
+ }
+ CacheState state = StateManager.getInstance().getCacheState(node);
+ switch (state) {
+ case consistent:
+ openInEditor(editorDescriptor, openUsingDescriptor);
+ break;
+ case modified: {
+ // If the file node's local cache has been modified, upload it
+ // before open it.
+ boolean successful = CacheManager.getInstance().upload(node);
+ if (successful) openInEditor(editorDescriptor, openUsingDescriptor);
+ }
+ break;
+ case outdated: {
+ // If the file node's local cache does not exist yet, download
+ // it.
+ boolean successful = CacheManager.getInstance().download(node);
+ if (successful) openInEditor(editorDescriptor, openUsingDescriptor);
+ }
+ break;
+ case conflict: {
+ String title = Messages.OpenFileHandler_ConflictingTitle;
+ String message = NLS.bind(Messages.OpenFileHandler_ConflictingMessage, node.name);
+ Shell parent = page.getWorkbenchWindow().getShell();
+ MessageDialog msgDialog = new MessageDialog(parent, title, null, message, MessageDialog.QUESTION, new String[] { Messages.OpenFileHandler_Merge, Messages.OpenFileHandler_OpenAnyway, Messages.OpenFileHandler_Cancel }, 0);
+ int index = msgDialog.open();
+ if (index == 0) {
+ LocalTypedElement local = new LocalTypedElement(node);
+ RemoteTypedElement remote = new RemoteTypedElement(node);
+ MergeEditorInput input = new MergeEditorInput(local, remote, page);
+ CompareUI.openCompareDialog(input);
+ }
+ else if (index == 1) {
+ openInEditor(editorDescriptor, openUsingDescriptor);
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ /**
+ * Open the editor using the specified editor descriptor. If openUsingDescriptor is true, it
+ * will try to use an external editor to open it if an eclipse editor is not available.
+ *
+ * @param editorDescriptor The editor descriptor used to open the node.
+ * @param openUsingDescriptor If an external editor should be used to open the node.
+ */
+ private void openInEditor(IEditorDescriptor editorDescriptor, boolean openUsingDescriptor) {
+ try {
+ IPath path = CacheManager.getInstance().getCachePath(node);
+ IFileStore fileStore = EFS.getLocalFileSystem().getStore(path);
+ FileStoreEditorInput input = new FileStoreEditorInput(fileStore);
+ if (openUsingDescriptor) {
+ String editorId = editorDescriptor.getId();
+ page.openEditor(input, editorId, true, IWorkbenchPage.MATCH_INPUT | IWorkbenchPage.MATCH_ID);
+ }
+ else {
+ String editorId = IEditorRegistry.SYSTEM_EXTERNAL_EDITOR_ID;
+ if (editorDescriptor != null) editorId = editorDescriptor.getId();
+ page.openEditor(input, editorId, true, IWorkbenchPage.MATCH_INPUT | IWorkbenchPage.MATCH_ID);
+ Map<QualifiedName, String> properties = PersistenceManager.getInstance()
+ .getPersistentProperties(node);
+ properties.put(IDE.EDITOR_KEY, editorId);
+ }
+ }
+ catch (PartInitException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/PersistenceManager.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/PersistenceManager.java
new file mode 100644
index 000000000..2b0d8405f
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/PersistenceManager.java
@@ -0,0 +1,510 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * William Chen (Wind River) [360494]Provide an "Open With" action in the pop
+ * up menu of file system nodes of Target Explorer.
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.handlers;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.QualifiedName;
+import org.eclipse.core.runtime.content.IContentType;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.tcf.te.tcf.filesystem.activator.UIPlugin;
+import org.eclipse.tcf.te.tcf.filesystem.internal.preferences.TargetExplorerPreferencePage;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSTreeNode;
+import org.eclipse.ui.IMemento;
+import org.eclipse.ui.WorkbenchException;
+import org.eclipse.ui.XMLMemento;
+
+/**
+ * A facility class to load and save persistent data such including resolved content types, file's
+ * properties, and time stamps etc.
+ */
+public class PersistenceManager {
+ // The XML element of unresolvable.
+ private static final String ELEMENT_UNRESOLVABLE = "unresolvable"; //$NON-NLS-1$
+
+ // The root element of "unresolvables"
+ private static final String ELEMENT_UNRESOLVED = "unresolved"; //$NON-NLS-1$
+
+ // The attribute "contentType" to specify the content type id of the file.
+ private static final String ATTR_CONTENT_TYPE = "contentType"; //$NON-NLS-1$
+
+ // The XML element of resolvable.
+ private static final String ELEMENT_RESOLVABLE = "resolvable"; //$NON-NLS-1$
+
+ // The root element of "resolvables"
+ private static final String ELEMENT_RESOLVED = "resolved"; //$NON-NLS-1$
+
+ // The root element of the memento for content type resolving.
+ private static final String CONTENT_TYPE_ROOT = "contentTypes"; //$NON-NLS-1$
+
+ // The XML file name used to store the resolved content types.
+ private static final String CONTENT_TYPE_FILE = "contentTypes.xml"; //$NON-NLS-1$
+
+ // The attribute "value"
+ private static final String ATTR_VALUE = "value"; //$NON-NLS-1$
+
+ // The attribute "local name" of a qualified name.
+ private static final String ATTR_LOCAL_NAME = "localName"; //$NON-NLS-1$
+
+ // The attribute "qualifier" of a qualified name.
+ private static final String ATTR_QUALIFIER = "qualifier"; //$NON-NLS-1$
+
+ // The attribute of a node's URL
+ private static final String ATTR_URL = "URL"; //$NON-NLS-1$
+
+ // The element "property" to record a file's property
+ private static final String ELEMENT_PROPERTY = "property"; //$NON-NLS-1$
+
+ // The element "file" to specify a file's entry.
+ private static final String ELEMENT_FILE = "file"; //$NON-NLS-1$
+
+ // The root element of properties.
+ private static final String PERSISTENT_ROOT = "properties"; //$NON-NLS-1$
+
+ // Time stamp file used to persist the time stamps of each file.
+ private static final String TIMESTAMP_FILE = "timestamps.xml"; //$NON-NLS-1$
+
+ // The file used to store persistent properties of each file.
+ private static final String PERSISTENT_FILE = "persistent.xml"; //$NON-NLS-1$
+
+ // The singleton instance.
+ private static PersistenceManager instance;
+
+ // The time stamp for each file.
+ private Map<URL, Long> timestamps;
+
+ // The persistent properties of the files.
+ private Map<URL, Map<QualifiedName, String>> properties;
+
+ // Already known resolved content type of file nodes specified by their URLs.
+ private Map<URL, IContentType> resolved;
+
+ // Already known unresolvable file nodes specified by their URLs.
+ private Map<URL, URL> unresolved;
+
+ /**
+ * Get the singleton cache manager.
+ *
+ * @return The singleton cache manager.
+ */
+ public static PersistenceManager getInstance() {
+ if (instance == null) {
+ instance = new PersistenceManager();
+ }
+ return instance;
+ }
+
+ /**
+ * Create a Persistent Manager instance.
+ */
+ private PersistenceManager() {
+ loadTimestamps();
+ loadPersistentProperties();
+ loadContentTypes();
+ }
+
+ /**
+ * If the node is already considered unresolvable.
+ *
+ * @param node The file node.
+ * @return true if it is not resolvable or else false.
+ */
+ public boolean isUnresovled(FSTreeNode node) {
+ return unresolved.get(node.getLocationURL()) != null;
+ }
+
+ /**
+ * Get the resolved content type of the node.
+ *
+ * @param node The file node.
+ * @return the content type of the node if it is resolvable or null.
+ */
+ public IContentType getResolved(FSTreeNode node) {
+ return resolved.get(node.getLocationURL());
+ }
+
+ /**
+ * Add the node and its content type to the resolved list.
+ *
+ * @param node The file node.
+ * @param contentType Its content type.
+ */
+ public void addResovled(FSTreeNode node, IContentType contentType) {
+ resolved.put(node.getLocationURL(), contentType);
+ }
+
+ /**
+ * Add the node as an unresolvable node.
+ *
+ * @param node The file node.
+ */
+ public void addUnresolved(FSTreeNode node) {
+ unresolved.put(node.getLocationURL(), node.getLocationURL());
+ }
+
+ /**
+ * If the option of "autosaving" is set to on.
+ *
+ * @return true if it is auto saving or else false.
+ */
+ public boolean isAutoSaving() {
+ IPreferenceStore preferenceStore = UIPlugin.getDefault().getPreferenceStore();
+ boolean autoSaving = preferenceStore
+ .getBoolean(TargetExplorerPreferencePage.PREF_AUTOSAVING);
+ return autoSaving;
+ }
+
+ /**
+ * Load the persistent properties from the persistent file in the cache's root directory.
+ */
+ private void loadPersistentProperties() {
+ IMemento memento = readMemento(PERSISTENT_FILE, PERSISTENT_ROOT);
+ properties = Collections.synchronizedMap(new HashMap<URL, Map<QualifiedName, String>>());
+ IMemento[] children = memento.getChildren(ELEMENT_FILE);
+ if (children != null && children.length > 0) {
+ for (IMemento child : children) {
+ try {
+ String str = child.getString(ATTR_URL);
+ URL url = new URL(str);
+ Map<QualifiedName, String> nodeProperties = loadFileProperties(child);
+ properties.put(url, nodeProperties);
+ }
+ catch (MalformedURLException e) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Load the content type information from the content type file.
+ */
+ private void loadContentTypes() {
+ IMemento memento = readMemento(CONTENT_TYPE_FILE, CONTENT_TYPE_ROOT);
+ resolved = Collections.synchronizedMap(new HashMap<URL, IContentType>());
+ unresolved = Collections.synchronizedMap(new HashMap<URL, URL>());
+ IMemento mResolved = memento.getChild(ELEMENT_RESOLVED);
+ if (mResolved != null) {
+ IMemento[] children = mResolved.getChildren(ELEMENT_RESOLVABLE);
+ if (children != null && children.length > 0) {
+ for (IMemento child : children) {
+ try {
+ String str = child.getString(ATTR_URL);
+ URL url = new URL(str);
+ String id = child.getString(ATTR_CONTENT_TYPE);
+ IContentType contentType = Platform.getContentTypeManager()
+ .getContentType(id);
+ if (contentType != null) {
+ resolved.put(url, contentType);
+ }
+ }
+ catch (MalformedURLException e) {
+ }
+ }
+ }
+ }
+ IMemento mUnresolved = memento.getChild(ELEMENT_UNRESOLVED);
+ if (mUnresolved != null) {
+ IMemento[] children = mUnresolved.getChildren(ELEMENT_UNRESOLVABLE);
+ if (children != null && children.length > 0) {
+ for (IMemento child : children) {
+ try {
+ String str = child.getString(ATTR_URL);
+ URL url = new URL(str);
+ unresolved.put(url, url);
+ }
+ catch (MalformedURLException e) {
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Save the content type information to the content type file.
+ */
+ private void saveContentTypes() {
+ XMLMemento memento = XMLMemento.createWriteRoot(CONTENT_TYPE_ROOT);
+ IMemento mResolved = memento.createChild(ELEMENT_RESOLVED);
+ for (URL key : resolved.keySet()) {
+ IContentType iContentType = resolved.get(key);
+ IMemento mResolvable = mResolved.createChild(ELEMENT_RESOLVABLE);
+ mResolvable.putString(ATTR_URL, key.toString());
+ mResolvable.putString(ATTR_CONTENT_TYPE, iContentType.getId());
+ }
+ IMemento mUnresolved = memento.createChild(ELEMENT_UNRESOLVED);
+ for (URL key : unresolved.keySet()) {
+ IMemento mUnresolvable = mUnresolved.createChild(ELEMENT_UNRESOLVABLE);
+ mUnresolvable.putString(ATTR_URL, key.toString());
+ }
+ writeMemento(memento, CONTENT_TYPE_FILE);
+ }
+
+ /**
+ * Load a file's properties from the memento node.
+ *
+ * @param memento The memento node.
+ * @return The properties as a map.
+ */
+ private Map<QualifiedName, String> loadFileProperties(IMemento memento) {
+ Map<QualifiedName, String> properties = Collections
+ .synchronizedMap(new HashMap<QualifiedName, String>());
+ IMemento[] children = memento.getChildren(ELEMENT_PROPERTY);
+ if (children != null && children.length > 0) {
+ for (IMemento child : children) {
+ String qualifier = child.getString(ATTR_QUALIFIER);
+ String localName = child.getString(ATTR_LOCAL_NAME);
+ QualifiedName name = new QualifiedName(qualifier, localName);
+ String value = child.getString(ATTR_VALUE);
+ properties.put(name, value);
+ }
+ }
+ return properties;
+ }
+
+ /**
+ * Read the memento from a memento file using the specified root element name.
+ *
+ * @param mementoFile The memento file.
+ * @param mementoRoot The memento's root element name.
+ * @return A memento of this file or an empty memento if the file does not exist.
+ */
+ private IMemento readMemento(String mementoFile, String mementoRoot) {
+ File location = CacheManager.getInstance().getCacheRoot();
+ File stateFile = new File(location, mementoFile);
+ if (stateFile.exists()) {
+ BufferedReader reader = null;
+ try {
+ FileInputStream input = new FileInputStream(stateFile);
+ reader = new BufferedReader(new InputStreamReader(input, "utf-8")); //$NON-NLS-1$
+ IMemento memento = XMLMemento.createReadRoot(reader);
+ return memento;
+ }
+ catch (IOException e) {
+ }
+ catch (WorkbenchException e) {
+ }
+ finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ }
+ catch (Exception e) {
+ }
+ }
+ }
+ }
+ return XMLMemento.createWriteRoot(mementoRoot);
+ }
+
+ /**
+ * Save the time stamps to the persistent file.
+ */
+ private void savePersistentProperties() {
+ XMLMemento memento = XMLMemento.createWriteRoot(PERSISTENT_ROOT);
+ for (URL key : properties.keySet()) {
+ Map<QualifiedName, String> nodeProperties = properties.get(key);
+ if (!nodeProperties.keySet().isEmpty()) {
+ IMemento mFile = memento.createChild(ELEMENT_FILE);
+ mFile.putString(ATTR_URL, key.toString());
+ saveFileProperties(mFile, nodeProperties);
+ }
+ }
+ writeMemento(memento, PERSISTENT_FILE);
+ }
+
+ /**
+ * Save the file's properties to a memento.
+ *
+ * @param memento The memento object.
+ * @param properties The file properties.
+ */
+ private void saveFileProperties(IMemento memento, Map<QualifiedName, String> properties) {
+ for (QualifiedName name : properties.keySet()) {
+ IMemento mProperty = memento.createChild(ELEMENT_PROPERTY);
+ mProperty.putString(ATTR_QUALIFIER, name.getQualifier());
+ mProperty.putString(ATTR_LOCAL_NAME, name.getLocalName());
+ mProperty.putString(ATTR_VALUE, properties.get(name));
+ }
+ }
+
+ /**
+ * Write the memento to a memento file.
+ *
+ * @param memento The memento object.
+ * @param mementoFile The file to write to.
+ */
+ private void writeMemento(XMLMemento memento, String mementoFile) {
+ OutputStreamWriter writer = null;
+ try {
+ File location = CacheManager.getInstance().getCacheRoot();
+ File stateFile = new File(location, mementoFile);
+ FileOutputStream stream = new FileOutputStream(stateFile);
+ writer = new OutputStreamWriter(stream, "utf-8"); //$NON-NLS-1$
+ memento.save(writer);
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+ finally {
+ if (writer != null) {
+ try {
+ writer.close();
+ }
+ catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Get the file properties of the specified node from the properties map.
+ *
+ * @param node The file node.
+ * @return The file properties object or empty properties object if it does not exist.
+ */
+ public Map<QualifiedName, String> getPersistentProperties(FSTreeNode node) {
+ Map<QualifiedName, String> nodeProperties = properties.get(node.getLocationURL());
+ if (nodeProperties == null) {
+ nodeProperties = Collections.synchronizedMap(new HashMap<QualifiedName, String>());
+ properties.put(node.getLocationURL(), nodeProperties);
+ }
+ return nodeProperties;
+ }
+
+ /**
+ * Load the time stamps from the time stamps file in the cache's root directory.
+ */
+ private void loadTimestamps() {
+ timestamps = Collections.synchronizedMap(new HashMap<URL, Long>());
+ File location = CacheManager.getInstance().getCacheRoot();
+ File tsFile = new File(location, TIMESTAMP_FILE);
+ if (tsFile.exists()) {
+ Properties properties = new Properties();
+ InputStream input = null;
+ try {
+ input = new BufferedInputStream(new FileInputStream(tsFile));
+ properties.loadFromXML(input);
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+ finally {
+ if (input != null) {
+ try {
+ input.close();
+ }
+ catch (IOException e) {
+ }
+ }
+ }
+ Enumeration<String> keys = (Enumeration<String>) properties.propertyNames();
+ while (keys.hasMoreElements()) {
+ String key = keys.nextElement();
+ String value = properties.getProperty(key);
+ long timestamp = 0L;
+ try {
+ timestamp = Long.parseLong(value);
+ timestamps.put(new URL(key), Long.valueOf(timestamp));
+ }
+ catch (Exception nfe) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Save the time stamps to the time stamps file.
+ */
+ private void saveTimestamps() {
+ Properties properties = new Properties();
+ for (URL key : timestamps.keySet()) {
+ Long timestamp = timestamps.get(key);
+ properties.setProperty(key.toString(), timestamp.toString());
+ }
+ File location = CacheManager.getInstance().getCacheRoot();
+ File fTimestamp = new File(location, TIMESTAMP_FILE);
+ OutputStream output = null;
+ try {
+ output = new BufferedOutputStream(new FileOutputStream(fTimestamp));
+ properties.storeToXML(output, null);
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+ finally {
+ if (output != null) {
+ try {
+ output.close();
+ }
+ catch (Exception e) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Set the time stamp of the FSTreeNode with the specified location.
+ *
+ * @param url The FSTreeNode's location URL.
+ * @param timestamp The new base time stamp to be set.
+ */
+ public void setBaseTimestamp(URL url, long timestamp) {
+ timestamps.put(url, Long.valueOf(timestamp));
+ }
+
+ /**
+ * Remove the time stamp entry with the specified URL.
+ *
+ * @param url The URL key.
+ */
+ public void removeBaseTimestamp(URL url) {
+ timestamps.remove(url);
+ }
+
+ /**
+ * Get the time stamp of the FSTreeNode with the specified location.
+ *
+ * @param url The FSTreeNode's location URL.
+ * @return The FSTreeNode's base time stamp.
+ */
+ public long getBaseTimestamp(URL url) {
+ Long timestamp = timestamps.get(url);
+ return timestamp == null ? 0L : timestamp.longValue();
+ }
+
+ /**
+ * Dispose the cache manager so that it has a chance to save the timestamps and the persistent
+ * properties.
+ */
+ public void dispose() {
+ saveTimestamps();
+ savePersistentProperties();
+ saveContentTypes();
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/RefreshHandler.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/RefreshHandler.java
new file mode 100644
index 000000000..58fd56868
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/RefreshHandler.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ * William Chen (Wind River)- [345552] Edit the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.handlers;
+
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.tcf.te.tcf.filesystem.internal.exceptions.TCFException;
+import org.eclipse.tcf.te.tcf.filesystem.internal.nls.Messages;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSTreeNode;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+/**
+ * The handler to refresh the state of a file.
+ */
+public class RefreshHandler extends AbstractHandler {
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.commands.AbstractHandler#execute(org.eclipse.core.commands.ExecutionEvent)
+ */
+ @Override
+ public Object execute(ExecutionEvent event) throws ExecutionException {
+ IStructuredSelection selection = (IStructuredSelection) HandlerUtil.getCurrentSelectionChecked(event);
+ FSTreeNode node = (FSTreeNode) selection.getFirstElement();
+ try {
+ StateManager.getInstance().refreshState(node);
+ } catch (TCFException e) {
+ Shell parent = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
+ MessageDialog.openError(parent, Messages.StateManager_RefreshFailureTitle, e.getLocalizedMessage());
+ }
+ return null;
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/RevertHandler.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/RevertHandler.java
new file mode 100644
index 000000000..4c457d83e
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/RevertHandler.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ * William Chen (Wind River)- [345552] Edit the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.handlers;
+
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSTreeNode;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+/**
+ * The handler to revert the content of the file from the remote file.
+ * This handler is enabled only when the file is modified or conflicting.
+ */
+public class RevertHandler extends AbstractHandler {
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.core.commands.AbstractHandler#execute(org.eclipse.core.commands.ExecutionEvent)
+ */
+ @Override
+ public Object execute(ExecutionEvent event) throws ExecutionException {
+ IStructuredSelection selection = (IStructuredSelection) HandlerUtil.getCurrentSelectionChecked(event);
+ FSTreeNode node = (FSTreeNode) selection.getFirstElement();
+ CacheManager.getInstance().download(node);
+ return null;
+ }
+
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/StateManager.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/StateManager.java
new file mode 100644
index 000000000..905a3f8f1
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/StateManager.java
@@ -0,0 +1,300 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ * William Chen (Wind River)- [345552] Edit the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.handlers;
+
+import java.io.File;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.tcf.protocol.IChannel;
+import org.eclipse.tcf.protocol.IPeer;
+import org.eclipse.tcf.protocol.IToken;
+import org.eclipse.tcf.services.IFileSystem;
+import org.eclipse.tcf.services.IFileSystem.DoneSetStat;
+import org.eclipse.tcf.services.IFileSystem.DoneStat;
+import org.eclipse.tcf.services.IFileSystem.FileAttrs;
+import org.eclipse.tcf.services.IFileSystem.FileSystemException;
+import org.eclipse.tcf.te.tcf.filesystem.internal.exceptions.TCFChannelException;
+import org.eclipse.tcf.te.tcf.filesystem.internal.exceptions.TCFException;
+import org.eclipse.tcf.te.tcf.filesystem.internal.exceptions.TCFFileSystemException;
+import org.eclipse.tcf.te.tcf.filesystem.internal.nls.Messages;
+import org.eclipse.tcf.te.tcf.filesystem.internal.url.Rendezvous;
+import org.eclipse.tcf.te.tcf.filesystem.model.CacheState;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSModel;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSTreeNode;
+import org.eclipse.tcf.te.tcf.core.Tcf;
+import org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager.DoneOpenChannel;
+
+/**
+ * This class provides several utility methods to get, update, commit
+ * or refresh a file node's state.
+ *
+ */
+public class StateManager {
+
+ // The singleton instance.
+ private static StateManager instance;
+
+ /**
+ * Get the singleton user manager.
+ *
+ * @return The singleton cache manager.
+ */
+ public static StateManager getInstance() {
+ if (instance == null) {
+ instance = new StateManager();
+ }
+ return instance;
+ }
+
+ /**
+ * Create a StateManager fInstance.
+ */
+ private StateManager() {
+ }
+
+ /**
+ * Update the state of the specified node.
+ *
+ * @param node The tree node whose state is going to be updated.
+ * @throws TCFException
+ */
+ public void updateState(FSTreeNode node) throws TCFException {
+ updateFileStat(node, true);
+ }
+
+ /**
+ * Refresh the state of the specified node.
+ *
+ * @param node The tree node whose state is going to be refreshed.
+ * @throws TCFException
+ */
+ public void refreshState(FSTreeNode node) throws TCFException {
+ updateFileStat(node, false);
+ }
+
+ /**
+ * Update the file's state of the specified tree node. Synchronize the time stamp of
+ * the file with the base time stamp and that of the remote file if sync is true.
+ *
+ * @param node The tree node whose file state is going to be updated.
+ * @param sync If its base time stamp and its remote file's time stamp should be synchronized.
+ */
+ private void updateFileStat(final FSTreeNode node, final boolean sync) throws TCFException {
+ IChannel channel = null;
+ try {
+ channel = openChannel(node.peerNode.getPeer());
+ if (channel != null) {
+ IFileSystem service = channel.getRemoteService(IFileSystem.class);
+ if (service != null) {
+ final TCFFileSystemException[] errors = new TCFFileSystemException[1];
+ final Rendezvous rendezvous = new Rendezvous();
+ String path = node.getLocation(true);
+ service.stat(path, new DoneStat() {
+ @Override
+ public void doneStat(IToken token, FileSystemException error, FileAttrs attrs) {
+ if (error == null) {
+ updateNodeAttr(node, attrs, sync);
+ } else {
+ String message = NLS.bind(Messages.StateManager_CannotGetFileStatMessage, new Object[]{node.name, error});
+ errors[0] = new TCFFileSystemException(message, error);
+ }
+ rendezvous.arrive();
+ }
+ });
+ try {
+ rendezvous.waiting(5000L);
+ } catch (InterruptedException e) {
+ String message = NLS.bind(Messages.StateManager_CannotGetFileStateMessage2, new Object[]{node.name, e});
+ errors[0] = new TCFFileSystemException(message, e);
+ }
+ if (errors[0] != null) {
+ throw errors[0];
+ }
+ }else{
+ String message = NLS.bind(Messages.StateManager_TCFNotProvideFSMessage, node.peerNode.getPeer().getID());
+ throw new TCFFileSystemException(message);
+ }
+ }
+ } finally {
+ if (channel != null)
+ channel.close();
+ }
+ }
+
+ /**
+ * Update the file attribute of the specified tree node with the specified value
+ * and synchronize its base timestamp and its remote file's timestamp if "sync" is true.
+ *
+ * @param node The tree node whose file attribute is to updated.
+ * @param attr The new file attribute.
+ * @param sync If the timestamps should be synchronized.
+ */
+ void updateNodeAttr(FSTreeNode node, FileAttrs attr, boolean sync){
+ node.attr = attr;
+ if (sync) {
+ File file = CacheManager.getInstance().getCacheFile(node);
+ Assert.isTrue(file.exists());
+ file.setLastModified(attr.mtime);
+ PersistenceManager.getInstance().setBaseTimestamp(node.getLocationURL(), attr.mtime);
+ }
+ FSModel.getInstance().fireNodeStateChanged(node);
+ }
+
+ /**
+ * Commit the content of the local file to the target.
+ *
+ * @param node The tree node whose local file is going to committed.
+ * @throws TCFException
+ */
+ public void commitState(final FSTreeNode node) throws TCFException {
+ File file = CacheManager.getInstance().getCacheFile(node);
+ Assert.isTrue(file.exists());
+ long mtime = file.lastModified();
+ // Create the new file attribute based on the file's last modified time.
+ IFileSystem.FileAttrs attrs = new IFileSystem.FileAttrs(node.attr.flags, node.attr.size, node.attr.uid, node.attr.gid, node.attr.permissions, node.attr.atime, mtime,
+ node.attr.attributes);
+ setFileAttrs(node, attrs);
+ }
+
+ /**
+ * Set the file's attributes using the new attributes.
+ *
+ * @param node The file's node.
+ * @param attrs The new file attributes.
+ * @throws TCFException
+ */
+ public void setFileAttrs(final FSTreeNode node, final IFileSystem.FileAttrs attrs) throws TCFException {
+ IChannel channel = null;
+ try {
+ channel = openChannel(node.peerNode.getPeer());
+ if (channel != null) {
+ IFileSystem service = channel.getRemoteService(IFileSystem.class);
+ if (service != null) {
+ final TCFFileSystemException[] errors = new TCFFileSystemException[1];
+ final Rendezvous rendezvous = new Rendezvous();
+ String path = node.getLocation(true);
+ service.setstat(path, attrs, new DoneSetStat() {
+ @Override
+ public void doneSetStat(IToken token, FileSystemException error) {
+ if (error == null) {
+ commitNodeAttr(node, attrs);
+ } else {
+ String message = NLS.bind(Messages.StateManager_CannotSetFileStateMessage, new Object[] { node.name, error });
+ errors[0] = new TCFFileSystemException(message, error);
+ }
+ rendezvous.arrive();
+ }
+ });
+ try {
+ rendezvous.waiting(5000L);
+ } catch (InterruptedException e) {
+ String message = NLS.bind(Messages.StateManager_CannotSetFileStateMessage2, new Object[] { node.name, e });
+ errors[0] = new TCFFileSystemException(message, e);
+ }
+ if (errors[0] != null) {
+ throw errors[0];
+ }
+ } else {
+ String message = NLS.bind(Messages.StateManager_TCFNotProvideFSMessage2, node.peerNode.getPeer().getID());
+ throw new TCFFileSystemException(message);
+ }
+ }
+ } finally {
+ if (channel != null)
+ channel.close();
+ }
+ }
+
+ /**
+ * Open a channel connected to the target represented by the peer.
+ *
+ * @return The channel or null if the operation fails.
+ */
+ private IChannel openChannel(final IPeer peer) throws TCFChannelException {
+ final Rendezvous rendezvous = new Rendezvous();
+ final TCFChannelException[] errors = new TCFChannelException[1];
+ final IChannel[] channels = new IChannel[1];
+ Tcf.getChannelManager().openChannel(peer, new DoneOpenChannel(){
+ @Override
+ public void doneOpenChannel(Throwable error, IChannel channel) {
+ if(error!=null){
+ String message = NLS.bind(Messages.TCFUtilities_OpeningFailureMessage,
+ new Object[]{peer.getID(), error.getLocalizedMessage()});
+ errors[0] = new TCFChannelException(message, error);
+ }else{
+ channels[0] = channel;
+ }
+ rendezvous.arrive();
+ }});
+ try {
+ rendezvous.waiting(5000L);
+ } catch (InterruptedException e) {
+ String message = NLS.bind(Messages.TCFUtilities_OpeningFailureMessage,
+ new Object[]{peer.getID(), e.getLocalizedMessage()});
+ errors[0] = new TCFChannelException(message, e);
+ }
+ if(errors[0] != null){
+ throw errors[0];
+ }
+ return channels[0];
+ }
+
+ /**
+ * Commit the file attribute of the specified tree node with the specified value.
+ *
+ * @param node The tree node whose file attribute is to committed.
+ * @param attr The new file attribute.
+ */
+ void commitNodeAttr(FSTreeNode node, FileAttrs attr){
+ node.attr = attr;
+ PersistenceManager.getInstance().setBaseTimestamp(node.getLocationURL(), attr.mtime);
+ FSModel.getInstance().fireNodeStateChanged(node);
+ }
+
+ /**
+ * Get the local file's state of the specified tree node. The local file must exist
+ * before calling this method to get its state.
+ *
+ * @param node The tree node whose local file state is going to retrieved.
+ * @return The tree node's latest cache state.
+ */
+ public CacheState getCacheState(FSTreeNode node) {
+ File file = CacheManager.getInstance().getCacheFile(node);
+ if(!file.exists())
+ return CacheState.consistent;
+ long ltime = file.lastModified();
+ long btime = PersistenceManager.getInstance().getBaseTimestamp(node.getLocationURL());
+ long mtime = 0;
+ if(node.attr!=null)
+ mtime = node.attr.mtime;
+ if(btime == ltime){
+ if(isUnchanged(mtime, btime))
+ return CacheState.consistent;
+ return CacheState.outdated;
+ }
+ if(isUnchanged(mtime, btime))
+ return CacheState.modified;
+ return CacheState.conflict;
+ }
+
+ /**
+ * Compare the modified time of the remote file and the base timestamp
+ * and see if they are equal to each other.
+ *
+ * @param mtime The modified time of the remote file.
+ * @param btime The base timestamp cached.
+ * @return true if they are equal in second precision.
+ */
+ private boolean isUnchanged(long mtime, long btime){
+ return Math.abs(mtime-btime)/1000 == 0;
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/TimeTriggeredProgressMonitorDialog.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/TimeTriggeredProgressMonitorDialog.java
new file mode 100644
index 000000000..63edaa2ac
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/TimeTriggeredProgressMonitorDialog.java
@@ -0,0 +1,240 @@
+/*******************************************************************************
+ * Copyright (c) 2010 IBM Corporation and others.
+ * 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:
+ * IBM - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.handlers;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.dialogs.ProgressMonitorDialog;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.swt.custom.BusyIndicator;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.PlatformUI;
+
+/**
+ * The TimeTriggeredProgressMonitorDialog is a progress monitor dialog that only
+ * opens if the runnable provided exceeds the specified long operation time.
+ *
+ * @since 3.7 - Copied from
+ * org.eclipse.ui.internal.operations.TimeTriggeredProgressMonitorDialog
+ */
+public class TimeTriggeredProgressMonitorDialog extends ProgressMonitorDialog {
+
+ /**
+ * The time considered to be the long operation time.
+ */
+ /* default */ int longOperationTime;
+
+ /**
+ * The time at which the dialog should be opened.
+ */
+ /* default */ long triggerTime = -1;
+
+ /**
+ * Whether or not we've already opened a dialog.
+ */
+ /* default */ boolean dialogOpened = false;
+
+ /**
+ * Wrapped monitor so we can check ticks and open the dialog when
+ * appropriate
+ */
+ private IProgressMonitor wrappedMonitor;
+
+ /**
+ * Create a new instance of the receiver.
+ *
+ * @param parent
+ * the parent of the dialog
+ * @param longOperationTime
+ * the time (in milliseconds) considered to be a long enough
+ * execution time to warrant opening a dialog.
+ */
+ public TimeTriggeredProgressMonitorDialog(Shell parent, int longOperationTime) {
+ super(parent);
+ setOpenOnRun(false);
+ this.longOperationTime = longOperationTime;
+ }
+
+ /**
+ * Create a monitor for the receiver that wrappers the super classes monitor.
+ *
+ */
+ public void createWrappedMonitor() {
+ wrappedMonitor = new IProgressMonitor() {
+
+ @SuppressWarnings("synthetic-access")
+ IProgressMonitor superMonitor = TimeTriggeredProgressMonitorDialog.super.getProgressMonitor();
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.core.runtime.IProgressMonitor#beginTask(java.lang.String, int)
+ */
+ @Override
+ public void beginTask(String name, int totalWork) {
+ superMonitor.beginTask(name, totalWork);
+ checkTicking();
+ }
+
+ /**
+ * Check if we have ticked in the last 800ms.
+ */
+ private void checkTicking() {
+ if (triggerTime < 0) {
+ triggerTime = System.currentTimeMillis() + longOperationTime;
+ }
+ if (!dialogOpened && System.currentTimeMillis() > triggerTime) {
+ open();
+ dialogOpened = true;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.core.runtime.IProgressMonitor#done()
+ */
+ @Override
+ public void done() {
+ superMonitor.done();
+ checkTicking();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.core.runtime.IProgressMonitor#internalWorked(double)
+ */
+ @Override
+ public void internalWorked(double work) {
+ superMonitor.internalWorked(work);
+ checkTicking();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.core.runtime.IProgressMonitor#isCanceled()
+ */
+ @Override
+ public boolean isCanceled() {
+ return superMonitor.isCanceled();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.core.runtime.IProgressMonitor#setCanceled(boolean)
+ */
+ @Override
+ public void setCanceled(boolean value) {
+ superMonitor.setCanceled(value);
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.core.runtime.IProgressMonitor#setTaskName(java.lang
+ * .String)
+ */
+ @Override
+ public void setTaskName(String name) {
+ superMonitor.setTaskName(name);
+ checkTicking();
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.core.runtime.IProgressMonitor#subTask(java.lang.String
+ * )
+ */
+ @Override
+ public void subTask(String name) {
+ superMonitor.subTask(name);
+ checkTicking();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.core.runtime.IProgressMonitor#worked(int)
+ */
+ @Override
+ public void worked(int work) {
+ superMonitor.worked(work);
+ checkTicking();
+
+ }
+ };
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jface.dialogs.ProgressMonitorDialog#getProgressMonitor()
+ */
+ @Override
+ public IProgressMonitor getProgressMonitor() {
+ if (wrappedMonitor == null) {
+ createWrappedMonitor();
+ }
+ return wrappedMonitor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jface.operations.IRunnableContext#run(boolean, boolean,
+ * IRunnableWithProgress)
+ */
+ @Override
+ public void run(final boolean fork, final boolean cancelable,
+ final IRunnableWithProgress runnable)
+ throws InvocationTargetException, InterruptedException {
+ final InvocationTargetException[] invokes = new InvocationTargetException[1];
+ final InterruptedException[] interrupt = new InterruptedException[1];
+ Runnable dialogWaitRunnable = new Runnable() {
+ @Override
+ @SuppressWarnings("synthetic-access")
+ public void run() {
+ try {
+ TimeTriggeredProgressMonitorDialog.super.run(fork, cancelable, runnable);
+ } catch (InvocationTargetException e) {
+ invokes[0] = e;
+ } catch (InterruptedException e) {
+ interrupt[0] = e;
+ }
+ }
+ };
+ final Display display = PlatformUI.getWorkbench().getDisplay();
+ if (display == null) {
+ return;
+ }
+ // show a busy cursor until the dialog opens
+ BusyIndicator.showWhile(display, dialogWaitRunnable);
+ if (invokes[0] != null) {
+ throw invokes[0];
+ }
+ if (interrupt[0] != null) {
+ throw interrupt[0];
+ }
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/UpdateHandler.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/UpdateHandler.java
new file mode 100644
index 000000000..23ab6d82d
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/UpdateHandler.java
@@ -0,0 +1,89 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ * William Chen (Wind River)- [345552] Edit the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.handlers;
+
+import java.io.File;
+
+import org.eclipse.compare.CompareUI;
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.tcf.te.tcf.filesystem.internal.compare.LocalTypedElement;
+import org.eclipse.tcf.te.tcf.filesystem.internal.compare.MergeEditorInput;
+import org.eclipse.tcf.te.tcf.filesystem.internal.compare.RemoteTypedElement;
+import org.eclipse.tcf.te.tcf.filesystem.internal.exceptions.TCFException;
+import org.eclipse.tcf.te.tcf.filesystem.internal.nls.Messages;
+import org.eclipse.tcf.te.tcf.filesystem.model.CacheState;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSTreeNode;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+/**
+ * The handler to update the local file's content with the latest of its remote file.
+ *
+ */
+public class UpdateHandler extends AbstractHandler {
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.core.commands.AbstractHandler#execute(org.eclipse.core.commands.ExecutionEvent)
+ */
+ @Override
+ public Object execute(ExecutionEvent event) throws ExecutionException {
+ IStructuredSelection selection = (IStructuredSelection) HandlerUtil.getCurrentSelectionChecked(event);
+ FSTreeNode node = (FSTreeNode) selection.getFirstElement();
+ try {
+ StateManager.getInstance().refreshState(node);
+ } catch (TCFException e) {
+ Shell parent = HandlerUtil.getActiveShell(event);
+ MessageDialog.openError(parent, Messages.StateManager_RefreshFailureTitle, e.getLocalizedMessage());
+ return null;
+ }
+ Shell parent = HandlerUtil.getActiveShell(event);
+ File file = CacheManager.getInstance().getCacheFile(node);
+ if(file.exists()){
+ CacheState state = StateManager.getInstance().getCacheState(node);
+ switch (state) {
+ case conflict:
+ String title = Messages.UpdateHandler_StateChangedDialogTitle;
+ String message = NLS.bind(Messages.UpdateHandler_StateChangedMessage, node.name);
+ MessageDialog msgDialog = new MessageDialog(parent, title, null, message,
+ MessageDialog.QUESTION, new String[]{Messages.UpdateHandler_Merge,
+ Messages.UpdateHandler_UpdateAnyway, Messages.UpdateHandler_Cancel}, 0);
+ int index = msgDialog.open();
+ if (index == 0) {
+ LocalTypedElement local = new LocalTypedElement(node);
+ RemoteTypedElement remote = new RemoteTypedElement(node);
+ IWorkbenchPage page = HandlerUtil.getActiveSite(event).getPage();
+ MergeEditorInput input = new MergeEditorInput(local, remote, page);
+ CompareUI.openCompareDialog(input);
+ }else if(index == 1){
+ CacheManager.getInstance().download(node);
+ }
+ break;
+ case modified:
+ break;
+ case consistent:
+ break;
+ case outdated:
+ CacheManager.getInstance().download(node);
+ break;
+ }
+ }else{
+ CacheManager.getInstance().download(node);
+ }
+ return null;
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/UserManager.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/UserManager.java
new file mode 100644
index 000000000..c929f200b
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/handlers/UserManager.java
@@ -0,0 +1,207 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ * William Chen (Wind River)- [345552] Edit the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.handlers;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.tcf.protocol.IChannel;
+import org.eclipse.tcf.protocol.IPeer;
+import org.eclipse.tcf.protocol.IToken;
+import org.eclipse.tcf.protocol.Protocol;
+import org.eclipse.tcf.services.IFileSystem;
+import org.eclipse.tcf.services.IFileSystem.DoneUser;
+import org.eclipse.tcf.services.IFileSystem.FileSystemException;
+import org.eclipse.tcf.te.tcf.filesystem.internal.UserAccount;
+import org.eclipse.tcf.te.tcf.filesystem.internal.exceptions.TCFChannelException;
+import org.eclipse.tcf.te.tcf.filesystem.internal.exceptions.TCFException;
+import org.eclipse.tcf.te.tcf.filesystem.internal.exceptions.TCFFileSystemException;
+import org.eclipse.tcf.te.tcf.filesystem.internal.nls.Messages;
+import org.eclipse.tcf.te.tcf.filesystem.internal.url.Rendezvous;
+import org.eclipse.tcf.te.tcf.core.Tcf;
+import org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager.DoneOpenChannel;
+import org.eclipse.tcf.te.tcf.locator.interfaces.nodes.IPeerModel;
+import org.eclipse.ui.PlatformUI;
+
+/**
+ * A facility class to retrieve the user's information for a target file system.
+ */
+public class UserManager {
+ // The key to save and retrieve the user account in a peer model.
+ private static final String USER_ACCOUNT_KEY = "user.account"; //$NON-NLS-1$
+
+ // The singleton fInstance.
+ private static UserManager instance;
+
+ /**
+ * Get the singleton user manager.
+ *
+ * @return The singleton cache manager.
+ */
+ public static UserManager getInstance() {
+ if (instance == null) {
+ instance = new UserManager();
+ }
+ return instance;
+ }
+
+ private UserManager() {
+ }
+
+ /**
+ * Get the user account from the peer using the channel connected to the
+ * remote target.
+ *
+ * @param channel
+ * The channel connected to the remote target.
+ * @return The user account information or null if it fails.
+ */
+ private UserAccount getUserByChannel(final IChannel channel) throws TCFFileSystemException {
+ IFileSystem service = channel.getRemoteService(IFileSystem.class);
+ if (service != null) {
+ final TCFFileSystemException[] errors = new TCFFileSystemException[1];
+ final Rendezvous rendezvous = new Rendezvous();
+ final UserAccount[] accounts = new UserAccount[1];
+ service.user(new DoneUser() {
+ @Override
+ public void doneUser(IToken token, FileSystemException error, int real_uid, int effective_uid, int real_gid, int effective_gid, String home) {
+ if (error == null) {
+ accounts[0] = new UserAccount(real_uid, real_gid, effective_uid, effective_gid, home);
+ }else {
+ String message = NLS.bind(Messages.UserManager_CannotGetUserAccountMessage, channel.getRemotePeer().getID());
+ errors[0] = new TCFFileSystemException(message, error);
+ }
+ rendezvous.arrive();
+ }
+ });
+ try {
+ rendezvous.waiting(5000L);
+ } catch (InterruptedException e) {
+ String message = NLS.bind(Messages.UserManager_CannotGetUserAccountMessage2, channel.getRemotePeer().getID());
+ errors[0] = new TCFFileSystemException(message, e);
+ }
+ if (errors[0] != null) {
+ throw errors[0];
+ }
+ return accounts[0];
+ }
+ String message = NLS.bind(Messages.UserManager_TCFNotProvideFSMessage, channel.getRemotePeer().getID());
+ throw new TCFFileSystemException(message);
+ }
+
+ /**
+ * Get the information of the client user account.
+ *
+ * @return The client user account's information.
+ */
+ public UserAccount getUserAccount(IPeerModel peerNode) {
+ UserAccount account = getUserFromPeer(peerNode);
+ if (account == null) {
+ IChannel channel = null;
+ try{
+ channel = openChannel(peerNode.getPeer());
+ if (channel != null) {
+ account = getUserByChannel(channel);
+ if (account != null)
+ setUserToPeer(peerNode, account);
+ }
+ }catch(TCFException e){
+ Shell parent = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
+ MessageDialog.openError(parent, Messages.UserManager_UserAccountTitle, e.getLocalizedMessage());
+ }finally{
+ if(channel!=null){
+ channel.close();
+ }
+ }
+ }
+ return account;
+ }
+
+ /**
+ * Open a channel connected to the target represented by the peer.
+ *
+ * @return The channel or null if the operation fails.
+ */
+ private IChannel openChannel(final IPeer peer) throws TCFChannelException {
+ final Rendezvous rendezvous = new Rendezvous();
+ final TCFChannelException[] errors = new TCFChannelException[1];
+ final IChannel[] channels = new IChannel[1];
+ Tcf.getChannelManager().openChannel(peer, new DoneOpenChannel(){
+ @Override
+ public void doneOpenChannel(Throwable error, IChannel channel) {
+ if(error!=null){
+ String message = NLS.bind(Messages.TCFUtilities_OpeningFailureMessage,
+ new Object[]{peer.getID(), error.getLocalizedMessage()});
+ errors[0] = new TCFChannelException(message, error);
+ }else{
+ channels[0] = channel;
+ }
+ rendezvous.arrive();
+ }});
+ try {
+ rendezvous.waiting(5000L);
+ } catch (InterruptedException e) {
+ String message = NLS.bind(Messages.TCFUtilities_OpeningFailureMessage,
+ new Object[]{peer.getID(), e.getLocalizedMessage()});
+ errors[0] = new TCFChannelException(message, e);
+ }
+ if(errors[0] != null){
+ throw errors[0];
+ }
+ return channels[0];
+ }
+
+ /**
+ * Get the user account stored in the specified peer model using a key named
+ * "user.account" defined by the constant USER_ACCOUNT_KEY.
+ *
+ * @param peer
+ * The peer model from which the user account is retrieved.
+ * @return The user account if it exists or null if not.
+ */
+ private UserAccount getUserFromPeer(final IPeerModel peer) {
+ if (Protocol.isDispatchThread()) {
+ return (UserAccount) peer.getProperty(USER_ACCOUNT_KEY);
+ }
+ final UserAccount[] accounts = new UserAccount[1];
+ Protocol.invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ accounts[0] = (UserAccount) peer.getProperty(USER_ACCOUNT_KEY);
+ }
+ });
+ return accounts[0];
+ }
+
+ /**
+ * Save the user account to the specified peer model using a key named
+ * "user.account" defined by the constant USER_ACCOUNT_KEY.
+ *
+ * @param peer
+ * The peer model to which the user account is saved.
+ */
+ private void setUserToPeer(final IPeerModel peer, final UserAccount account) {
+ Assert.isNotNull(peer);
+ Assert.isNotNull(account);
+
+ if (Protocol.isDispatchThread()) {
+ peer.setProperty(USER_ACCOUNT_KEY, account);
+ } else {
+ Protocol.invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ peer.setProperty(USER_ACCOUNT_KEY, account);
+ }
+ });
+ }
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/help/IContextHelpIds.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/help/IContextHelpIds.java
new file mode 100644
index 000000000..841713f80
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/help/IContextHelpIds.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.help;
+
+import org.eclipse.tcf.te.tcf.filesystem.activator.UIPlugin;
+
+/**
+ * Plugin context help id definitions.
+ */
+public interface IContextHelpIds {
+
+ /**
+ * Target Explorer file system UI plug-in common context help id prefix.
+ */
+ public final static String PREFIX = UIPlugin.getUniqueIdentifier() + "."; //$NON-NLS-1$
+
+ /**
+ /**
+ * Target Explorer details editor page: File system explorer
+ */
+ public final static String FS_EXPLORER_EDITOR_PAGE = PREFIX + "FSExplorerEditorPage"; //$NON-NLS-1$
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/nls/Messages.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/nls/Messages.java
new file mode 100644
index 000000000..4f53d7fea
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/nls/Messages.java
@@ -0,0 +1,221 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ * William Chen (Wind River) - [345384] Provide property pages for remote file system nodes
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.nls;
+
+import java.lang.reflect.Field;
+
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * File System plug-in externalized strings management.
+ */
+public class Messages extends NLS {
+
+ // The plug-in resource bundle name
+ private static final String BUNDLE_NAME = "org.eclipse.tcf.te.tcf.filesystem.internal.nls.Messages"; //$NON-NLS-1$
+
+ /**
+ * Static constructor.
+ */
+ static {
+ // Load message values from bundle file
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ /**
+ * Returns if or if not this NLS manager contains a constant for
+ * the given externalized strings key.
+ *
+ * @param key The externalized strings key or <code>null</code>.
+ * @return <code>True</code> if a constant for the given key exists, <code>false</code> otherwise.
+ */
+ public static boolean hasString(String key) {
+ if (key != null) {
+ try {
+ Field field = Messages.class.getDeclaredField(key);
+ return field != null;
+ } catch (NoSuchFieldException e) { /* ignored on purpose */ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the corresponding string for the given externalized strings
+ * key or <code>null</code> if the key does not exist.
+ *
+ * @param key The externalized strings key or <code>null</code>.
+ * @return The corresponding string or <code>null</code>.
+ */
+ public static String getString(String key) {
+ if (key != null) {
+ try {
+ Field field = Messages.class.getDeclaredField(key);
+ if (field != null) {
+ return (String)field.get(null);
+ }
+ } catch (Exception e) { /* ignored on purpose */ }
+ }
+
+ return null;
+ }
+
+ // **** Declare externalized string id's down here *****
+
+ public static String AdvancedAttributesDialog_Archive;
+ public static String AdvancedAttributesDialog_ArchiveIndex;
+ public static String AdvancedAttributesDialog_Compress;
+ public static String AdvancedAttributesDialog_Compressed;
+ public static String AdvancedAttributesDialog_CompressEncrypt;
+ public static String AdvancedAttributesDialog_Device;
+ public static String AdvancedAttributesDialog_Directory;
+ public static String AdvancedAttributesDialog_Encrypt;
+ public static String AdvancedAttributesDialog_Encrypted;
+ public static String AdvancedAttributesDialog_FileArchive;
+ public static String AdvancedAttributesDialog_FileBanner;
+ public static String AdvancedAttributesDialog_FolderArchive;
+ public static String AdvancedAttributesDialog_FolderBanner;
+ public static String AdvancedAttributesDialog_Hidden;
+ public static String AdvancedAttributesDialog_Indexed;
+ public static String AdvancedAttributesDialog_IndexFile;
+ public static String AdvancedAttributesDialog_IndexFolder;
+ public static String AdvancedAttributesDialog_Normal;
+ public static String AdvancedAttributesDialog_Offline;
+ public static String AdvancedAttributesDialog_ReadOnly;
+ public static String AdvancedAttributesDialog_Reparse;
+ public static String AdvancedAttributesDialog_ShellTitle;
+ public static String AdvancedAttributesDialog_Sparse;
+ public static String AdvancedAttributesDialog_System;
+ public static String AdvancedAttributesDialog_Temporary;
+ public static String AdvancedAttributesDialog_Virtual;
+
+ public static String CacheManager_Bytes;
+ public static String CacheManager_DowloadingFile;
+ public static String CacheManager_DownloadingError;
+ public static String CacheManager_DownloadingProgress;
+ public static String CacheManager_KBs;
+ public static String CacheManager_MBs;
+ public static String CacheManager_UploadingProgress;
+ public static String CacheManager_UploadNFiles;
+ public static String CacheManager_UploadSingleFile;
+
+ public static String CmmitHandler_Cancel;
+ public static String CmmitHandler_CommitAnyway;
+ public static String CmmitHandler_ErrorTitle;
+ public static String CmmitHandler_FileDeleted;
+ public static String CmmitHandler_Merge;
+ public static String CmmitHandler_StateChangedDialogTitle;
+ public static String CmmitHandler_StateChangedMessage;
+
+ public static String GeneralInformationPage_Accessed;
+ public static String GeneralInformationPage_Advanced;
+ public static String GeneralInformationPage_Attributes;
+ public static String GeneralInformationPage_Computer;
+ public static String GeneralInformationPage_File;
+ public static String GeneralInformationPage_Folder;
+ public static String GeneralInformationPage_Hidden;
+ public static String GeneralInformationPage_Location;
+ public static String GeneralInformationPage_Modified;
+ public static String GeneralInformationPage_Name;
+ public static String GeneralInformationPage_ReadOnly;
+ public static String GeneralInformationPage_Size;
+ public static String GeneralInformationPage_Type;
+ public static String GeneralInformationPage_PropertiesChangeFailure;
+ public static String GeneralInformationPage_PropertiesChangeTitle;
+ public static String GeneralInformationPage_UnknownFileType;
+
+ public static String FSExplorerTreeControl_section_title;
+
+ public static String FSTreeControl_column_name_label;
+ public static String FSTreeControl_column_size_label;
+ public static String FSTreeControl_column_modified_label;
+
+ public static String FSOpenFileDialog_title;
+
+ public static String LocalTypedElement_SavingFile;
+
+ public static String OpenFileHandler_Cancel;
+ public static String OpenFileHandler_ConflictingMessage;
+ public static String OpenFileHandler_ConflictingTitle;
+ public static String OpenFileHandler_Merge;
+ public static String OpenFileHandler_OpenAnyway;
+ public static String OpenFileHandler_OpeningBinaryNotSupported;
+ public static String OpenFileHandler_Warning;
+
+ public static String OpenWithMenu_ChooseEditorForOpening;
+ public static String OpenWithMenu_DefaultEditor;
+ public static String OpenWithMenu_NoEditorFound;
+ public static String OpenWithMenu_OpenWith;
+
+ public static String PermissionsGroup_Executable;
+ public static String PermissionsGroup_GroupPermissions;
+ public static String PermissionsGroup_OtherPermissions;
+ public static String PermissionsGroup_Readable;
+ public static String PermissionsGroup_UserPermissions;
+ public static String PermissionsGroup_Writable;
+
+ public static String RemoteTypedElement_GettingRemoteContent;
+
+ public static String SaveAllListener_Cancel;
+ public static String SaveAllListener_Merge;
+ public static String SaveAllListener_SaveAnyway;
+ public static String SaveAllListener_SingularMessage;
+ public static String SaveAllListener_StateChangedDialogTitle;
+
+ public static String SaveListener_Cancel;
+ public static String SaveListener_Merge;
+ public static String SaveListener_SaveAnyway;
+ public static String SaveListener_StateChangedDialogTitle;
+ public static String SaveListener_StateChangedMessage;
+
+ public static String StateManager_CannotGetFileStateMessage2;
+ public static String StateManager_CannotGetFileStatMessage;
+ public static String StateManager_CannotSetFileStateMessage;
+ public static String StateManager_CannotSetFileStateMessage2;
+ public static String StateManager_CommitFailureTitle;
+ public static String StateManager_RefreshFailureTitle;
+ public static String StateManager_TCFNotProvideFSMessage;
+ public static String StateManager_TCFNotProvideFSMessage2;
+ public static String StateManager_UpdateFailureTitle;
+
+ public static String TcfInputStream_CloseTimeout;
+ public static String TcfInputStream_NoDataAvailable;
+ public static String TcfInputStream_NoFileReturned;
+ public static String TcfInputStream_NoFSServiceAvailable;
+ public static String TcfInputStream_OpenFileTimeout;
+ public static String TcfInputStream_OpenTCFTimeout;
+ public static String TcfInputStream_ReadTimeout;
+ public static String TcfInputStream_StreamClosed;
+
+ public static String TcfOutputStream_StreamClosed;
+ public static String TcfOutputStream_WriteTimeout;
+
+ public static String TcfURLConnection_CloseFileTimeout;
+ public static String TcfURLConnection_NoFileHandleReturned;
+ public static String TcfURLConnection_NoFSServiceAvailable;
+ public static String TcfURLConnection_NoSuchTcfAgent;
+ public static String TcfURLConnection_OpenFileTimeout;
+ public static String TcfURLConnection_OpenTCFChannelTimeout;
+
+ public static String TCFUtilities_OpeningFailureMessage;
+ public static String TCFUtilities_OpeningFailureTitle;
+
+ public static String UpdateHandler_Cancel;
+ public static String UpdateHandler_Merge;
+ public static String UpdateHandler_StateChangedDialogTitle;
+ public static String UpdateHandler_StateChangedMessage;
+ public static String UpdateHandler_UpdateAnyway;
+
+ public static String UserManager_CannotGetUserAccountMessage;
+ public static String UserManager_CannotGetUserAccountMessage2;
+ public static String UserManager_TCFNotProvideFSMessage;
+ public static String UserManager_UserAccountTitle;
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/nls/Messages.properties b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/nls/Messages.properties
new file mode 100644
index 000000000..1744e2a71
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/nls/Messages.properties
@@ -0,0 +1,153 @@
+#
+# org.eclipse.tcf.te.tcf.filesystem
+# Externalized Strings.
+#
+
+FSExplorerTreeControl_section_title=Exploring File System
+
+FSTreeControl_column_name_label=Name
+FSTreeControl_column_size_label=Size
+FSTreeControl_column_modified_label=Date Modified
+
+FSOpenFileDialog_title=Select Process Image
+
+GeneralInformationPage_Accessed=Accessed:
+GeneralInformationPage_Advanced=\ A&dvanced...
+GeneralInformationPage_Attributes=Attributes:
+GeneralInformationPage_File=File ({0})
+GeneralInformationPage_Folder=Folder
+GeneralInformationPage_Hidden=Hidden
+GeneralInformationPage_ReadOnly=Read-only
+GeneralInformationPage_Computer=computer
+GeneralInformationPage_Location=Location:
+GeneralInformationPage_Modified=Modified:
+GeneralInformationPage_Name=Name:
+GeneralInformationPage_Size=Size:
+GeneralInformationPage_Type=Type:
+GeneralInformationPage_PropertiesChangeFailure=Properties changes to {0} failed due to the following reason:{1}
+GeneralInformationPage_PropertiesChangeTitle=Properties Change
+GeneralInformationPage_UnknownFileType=Unknown File
+
+AdvancedAttributesDialog_Archive=Archive
+AdvancedAttributesDialog_ArchiveIndex=Archive and Index attributes
+AdvancedAttributesDialog_Compress=Compress contents to save disk space
+AdvancedAttributesDialog_Compressed=Compressed
+AdvancedAttributesDialog_CompressEncrypt=Compress or Encrypt attributes
+AdvancedAttributesDialog_Device=Device
+AdvancedAttributesDialog_Directory=Directory
+AdvancedAttributesDialog_Encrypt=Encrypt contents to secure data
+AdvancedAttributesDialog_Encrypted=Encrypted
+AdvancedAttributesDialog_FileArchive=File is ready for archiving
+AdvancedAttributesDialog_FileBanner=Choose the options you want for this file.
+AdvancedAttributesDialog_FolderArchive=Folder is ready for archiving
+AdvancedAttributesDialog_FolderBanner=Choose the settings you want for this folder.\n\nWhen you apply these changes you will be asked if you want the\n changes to affect all subfolders and files as well.
+AdvancedAttributesDialog_Hidden=Hidden
+AdvancedAttributesDialog_Indexed=Indexed
+AdvancedAttributesDialog_IndexFile=For fast searching, allow Indexing Service to index this file
+AdvancedAttributesDialog_IndexFolder=For fast searching, allow Indexing Service to index this file
+AdvancedAttributesDialog_Normal=Normal
+AdvancedAttributesDialog_Offline=Offline
+AdvancedAttributesDialog_ReadOnly=Read-only
+AdvancedAttributesDialog_Reparse=Reparse
+AdvancedAttributesDialog_ShellTitle=Advanced Attributes
+AdvancedAttributesDialog_Sparse=Sparse
+AdvancedAttributesDialog_System=System
+AdvancedAttributesDialog_Temporary=Temporary
+AdvancedAttributesDialog_Virtual=Virtual
+
+CacheManager_Bytes=\ bytes
+CacheManager_DowloadingFile=Downloading file {0}...
+CacheManager_DownloadingError=Downloading Error
+CacheManager_DownloadingProgress=Downloading {0}/{1}.
+CacheManager_KBs=\ KBs
+CacheManager_MBs=\ MBs
+CacheManager_UploadingProgress=Uploading file {0}: {1}/{2}
+CacheManager_UploadNFiles=Uploading {0} files...
+CacheManager_UploadSingleFile=Uploading file {0}...
+
+CmmitHandler_Cancel=Cancel
+CmmitHandler_CommitAnyway=Commit anyway
+CmmitHandler_ErrorTitle=File Deleted
+CmmitHandler_FileDeleted=The local file {0} that you are trying to commit has been deleted\!
+CmmitHandler_Merge=Merge
+CmmitHandler_StateChangedDialogTitle=State Changed
+CmmitHandler_StateChangedMessage={0} on the target has changed and is conflicting with the local file. What do you want?
+
+LocalTypedElement_SavingFile=Saving file:
+
+OpenFileHandler_Cancel=Cancel
+OpenFileHandler_ConflictingMessage=The local {0} is conflicting with the remote version. What do you want?
+OpenFileHandler_ConflictingTitle=Conflicting content
+OpenFileHandler_Merge=Merge
+OpenFileHandler_OpenAnyway=Open anyway
+OpenFileHandler_OpeningBinaryNotSupported=Opening a binary file is not supported yet.
+OpenFileHandler_Warning=Warning
+
+OpenWithMenu_ChooseEditorForOpening=Choose the editor for opening {0}
+OpenWithMenu_DefaultEditor=&Default Editor
+OpenWithMenu_NoEditorFound=No editor found to edit the file resource.
+OpenWithMenu_OpenWith=Open With
+
+PermissionsGroup_Executable=Executable
+PermissionsGroup_GroupPermissions=Group:
+PermissionsGroup_OtherPermissions=Other:
+PermissionsGroup_Readable=Readable
+PermissionsGroup_UserPermissions=User:
+PermissionsGroup_Writable=Writable
+
+RemoteTypedElement_GettingRemoteContent=Getting content from the remote file:
+
+SaveAllListener_Cancel=Cancel
+SaveAllListener_Merge=Merge
+SaveAllListener_SaveAnyway=Save anyway
+SaveAllListener_SingularMessage=The file {0} on the target has changed and is conflicting with the local file. What do you want?
+SaveAllListener_StateChangedDialogTitle=State Changed
+
+SaveListener_Cancel=Cancel
+SaveListener_Merge=Merge
+SaveListener_SaveAnyway=Save anyway
+SaveListener_StateChangedDialogTitle=State Changed
+SaveListener_StateChangedMessage={0} on the target has changed and is conflicting with the local file. What do you want?
+
+StateManager_CannotGetFileStateMessage2=Cannot get the file's stat of {0}\!. Caused by {1}
+StateManager_CannotGetFileStatMessage=Cannot get the file's stat of {0}\!. Caused by {1}
+StateManager_CannotSetFileStateMessage=Cannot set the file's stat of {0}\!. Caused by {1}
+StateManager_CannotSetFileStateMessage2=Cannot set the file's stat of {0}\!. Caused by {1}
+StateManager_CommitFailureTitle=Commit Failure
+StateManager_RefreshFailureTitle=Refresh Failure
+StateManager_TCFNotProvideFSMessage=This TCF agent, {0}, does not provide a file system service\!
+StateManager_TCFNotProvideFSMessage2=This TCF agent, {0}, does not provide a file system service\!
+StateManager_UpdateFailureTitle=Update Failure
+
+TcfInputStream_CloseTimeout=Closing has timed out\!
+TcfInputStream_NoDataAvailable=No data available
+TcfInputStream_NoFileReturned=No file handle returned\!
+TcfInputStream_NoFSServiceAvailable=No remote File System Service available\!
+TcfInputStream_OpenFileTimeout=Opening file has timed out\!
+TcfInputStream_OpenTCFTimeout=Opening TCF channel has timed out\!
+TcfInputStream_ReadTimeout=Reading has timed out\!
+TcfInputStream_StreamClosed=Stream is already closed\!
+
+TcfOutputStream_StreamClosed=Stream is already closed\!
+TcfOutputStream_WriteTimeout=Writing has timed out\!
+
+TcfURLConnection_CloseFileTimeout=Closing has timed out\!
+TcfURLConnection_NoFileHandleReturned=No file handle returned\!
+TcfURLConnection_NoFSServiceAvailable=No remote File System Service available\!
+TcfURLConnection_NoSuchTcfAgent=TCF agent is already disconnected\!
+TcfURLConnection_OpenFileTimeout=Opening file has timed out\!
+TcfURLConnection_OpenTCFChannelTimeout=Opening TCF channel has timed out\!
+
+TCFUtilities_OpeningFailureMessage=We cannot open a TCF channel to the target: {0}. It is caused by {1}.
+TCFUtilities_OpeningFailureTitle=Opening Channel
+
+UpdateHandler_Cancel=Cancel
+UpdateHandler_Merge=Merge
+UpdateHandler_StateChangedDialogTitle=State Changed
+UpdateHandler_StateChangedMessage=The local {0} has changed and is conflicting with the remote file. What do you want?
+UpdateHandler_UpdateAnyway=Update anyway
+
+UserManager_CannotGetUserAccountMessage=Cannot get the user account from the agent {0}
+UserManager_CannotGetUserAccountMessage2=Cannot get the user account from the agent {0}. Caused by: networking too slow.
+UserManager_TCFNotProvideFSMessage=This TCF agent, {0}, does not provide a file system service\!
+UserManager_UserAccountTitle=User Account
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/preferences/PreferencesInitializer.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/preferences/PreferencesInitializer.java
new file mode 100644
index 000000000..bfb93db5e
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/preferences/PreferencesInitializer.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.preferences;
+
+import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
+import org.eclipse.core.runtime.preferences.DefaultScope;
+import org.eclipse.core.runtime.preferences.IEclipsePreferences;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.tcf.te.tcf.filesystem.activator.UIPlugin;
+import org.eclipse.tcf.te.tcf.filesystem.interfaces.preferences.IPreferenceKeys;
+
+
+/**
+ * The bundle's preference initializer implementation.
+ */
+public class PreferencesInitializer extends AbstractPreferenceInitializer {
+
+ /**
+ * Constructor.
+ */
+ public PreferencesInitializer() {
+ super();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer#initializeDefaultPreferences()
+ */
+ @Override
+ public void initializeDefaultPreferences() {
+ // Get the bundles preferences manager
+ IEclipsePreferences prefs = DefaultScope.INSTANCE.getNode(UIPlugin.getUniqueIdentifier());
+ if (prefs != null) {
+ // [Hidden] Editor content contribution: default on
+ prefs.putBoolean(IPreferenceKeys.PREF_FEATURE_ENABLE_EDITOR_CONTENT_CONTRIBUTION, true);
+ }
+ IPreferenceStore preferenceStore = UIPlugin.getDefault().getPreferenceStore();
+ preferenceStore.setDefault(TargetExplorerPreferencePage.PREF_AUTOSAVING, TargetExplorerPreferencePage.DEFAULT_AUTOSAVING);
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/preferences/TargetExplorerPreferencePage.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/preferences/TargetExplorerPreferencePage.java
new file mode 100644
index 000000000..17ffa341c
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/preferences/TargetExplorerPreferencePage.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * Wind River Systems - initial API and implementation
+ * William Chen (Wind River)- [345552] Edit the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.preferences;
+
+import org.eclipse.jface.preference.BooleanFieldEditor;
+import org.eclipse.jface.preference.FieldEditorPreferencePage;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.tcf.te.tcf.filesystem.activator.UIPlugin;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPreferencePage;
+
+/**
+ * The preference page for configuring the preference options for Target
+ * Explorer File System Explorer.
+ *
+ */
+public class TargetExplorerPreferencePage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage {
+ // The preference key to access the option of auto saving
+ public static final String PREF_AUTOSAVING = "PrefAutoSaving"; //$NON-NLS-1$
+ // The default value of the option of auto saving.
+ public static final boolean DEFAULT_AUTOSAVING = true;
+
+ // The editor to edit the value of auto saving.
+ protected BooleanFieldEditor fAutoSaving;
+
+ /***
+ * Create a preference page for Target Explorer File System Explorer.
+ */
+ public TargetExplorerPreferencePage() {
+ super(GRID);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.preference.FieldEditorPreferencePage#createFieldEditors()
+ */
+ @Override
+ protected void createFieldEditors() {
+ UIPlugin plugin = UIPlugin.getDefault();
+ IPreferenceStore preferenceStore = plugin.getPreferenceStore();
+ setPreferenceStore(preferenceStore);
+ fAutoSaving = new BooleanFieldEditor(PREF_AUTOSAVING, "Automatically upload files to targets upon saving.", //$NON-NLS-1$
+ getFieldEditorParent());
+ addField(fAutoSaving);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.IWorkbenchPreferencePage#init(org.eclipse.ui.IWorkbench)
+ */
+ @Override
+ public void init(IWorkbench workbench) {
+ // do nothing
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/properties/AdvancedAttributesDialog.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/properties/AdvancedAttributesDialog.java
new file mode 100644
index 000000000..ebc45be59
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/properties/AdvancedAttributesDialog.java
@@ -0,0 +1,225 @@
+/*********************************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * William Chen (Wind River) - [345384]Provide property pages for remote file system nodes
+ *********************************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.properties;
+
+import java.net.URL;
+
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.tcf.te.tcf.filesystem.activator.UIPlugin;
+import org.eclipse.tcf.te.tcf.filesystem.interfaces.IWindowsFileAttributes;
+import org.eclipse.tcf.te.tcf.filesystem.internal.nls.Messages;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSTreeNode;
+
+/**
+ * The dialog used to display the advanced attributes of a Windows file or
+ * folder.
+ */
+public class AdvancedAttributesDialog extends Dialog {
+ // The key to store the banner image in the plug-in's image registry.
+ private static final String BANNER_IMAGE_KEY = "BannerImage"; //$NON-NLS-1$
+ // The path to the image used in the banner.
+ private static final String BANNER_IMAGE_PATH = "icons/obj32/banner.png"; //$NON-NLS-1$
+
+ // The file or folder node whose advanced attributes are to be displayed.
+ FSTreeNode node;
+
+ /**
+ * Create the advanced attributes dialog with the specified node and a
+ * parent shell.
+ *
+ * @param parentShell
+ * The parent shell.
+ * @param node
+ * The file or folder node to be displayed.
+ */
+ public AdvancedAttributesDialog(Shell parentShell, FSTreeNode node) {
+ super(parentShell);
+ this.node = node;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite)
+ */
+ @Override
+ protected Control createDialogArea(Composite parent) {
+ Composite composite = (Composite) super.createDialogArea(parent);
+ Composite banner = new Composite(composite, SWT.NONE);
+ GridLayout layout = new GridLayout(2, false);
+ banner.setLayout(layout);
+ Label label = new Label(banner, SWT.NONE);
+ Image bImg = getBannerImage();
+ label.setImage(bImg);
+ label.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
+ label = new Label(banner, SWT.NONE);
+ if (node.isFile()) {
+ label.setText(Messages.AdvancedAttributesDialog_FileBanner);
+ } else if (node.isDirectory()) {
+ label.setText(Messages.AdvancedAttributesDialog_FolderBanner);
+ }
+ createArchiveAndIndexGroup(composite);
+ createCompressAndEncryptGroup(composite);
+ return composite;
+ }
+
+ /**
+ * Get the image in the banner area.
+ *
+ * @return The image in the banner area.
+ */
+ private Image getBannerImage() {
+ Image bImg = UIPlugin.getImage(BANNER_IMAGE_KEY);
+ if (bImg == null) {
+ URL bannerUrl = UIPlugin.getDefault().getBundle().getResource(BANNER_IMAGE_PATH);
+ ImageDescriptor desc = ImageDescriptor.createFromURL(bannerUrl);
+ UIPlugin.getDefault().getImageRegistry().put(BANNER_IMAGE_KEY, desc);
+ bImg = UIPlugin.getImage(BANNER_IMAGE_KEY);
+ }
+ return bImg;
+ }
+
+ /**
+ * Create the compress and encrypt options group.
+ *
+ * @param parent
+ * The parent composite where they are created.
+ */
+ private void createCompressAndEncryptGroup(Composite parent) {
+ Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
+ group.setText(Messages.AdvancedAttributesDialog_CompressEncrypt);
+ group.setLayout(new GridLayout());
+ group.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
+ createCompress(group);
+ createEncrypt(group);
+ }
+
+ /**
+ * Create the archive and indexing options group.
+ *
+ * @param parent
+ * The parent composite where they are created.
+ */
+ private void createArchiveAndIndexGroup(Composite parent) {
+ Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
+ group.setText(Messages.AdvancedAttributesDialog_ArchiveIndex);
+ group.setLayout(new GridLayout());
+ group.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
+ createArchive(group);
+ createIndexField(group);
+ }
+
+ /**
+ * Create the indexing option field.
+ *
+ * @param group
+ * The group widget where the field is created.
+ */
+ private void createIndexField(Group group) {
+ String label = node.isFile() ? Messages.AdvancedAttributesDialog_IndexFile
+ : (node.isDirectory() ? Messages.AdvancedAttributesDialog_IndexFolder
+ : null);
+ boolean on = !node.isWin32AttrOn(IWindowsFileAttributes.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED);
+ createOptionField(group, label, IWindowsFileAttributes.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, on);
+ }
+
+ /**
+ * Create the archive option field.
+ *
+ * @param group
+ * The group widget where the field is created.
+ */
+ private void createArchive(Group group) {
+ String label = node.isFile() ? Messages.AdvancedAttributesDialog_FileArchive
+ : (node.isDirectory() ? Messages.AdvancedAttributesDialog_FolderArchive
+ : null);
+ boolean on = node.isWin32AttrOn(IWindowsFileAttributes.FILE_ATTRIBUTE_ARCHIVE);
+ createOptionField(group, label, IWindowsFileAttributes.FILE_ATTRIBUTE_ARCHIVE, on);
+ }
+
+ /**
+ * Create the encrypt option field.
+ *
+ * @param group
+ * The group widget where the field is created.
+ */
+ private void createEncrypt(Group group) {
+ String label = Messages.AdvancedAttributesDialog_Encrypt;
+ boolean on = node.isWin32AttrOn(IWindowsFileAttributes.FILE_ATTRIBUTE_ENCRYPTED);
+ createOptionField(group, label, IWindowsFileAttributes.FILE_ATTRIBUTE_ENCRYPTED, on);
+ }
+
+ /**
+ * Create the compress option field.
+ *
+ * @param group
+ * The group widget where the field is created.
+ */
+ private void createCompress(Group group) {
+ String label = Messages.AdvancedAttributesDialog_Compress;
+ boolean on = node.isWin32AttrOn(IWindowsFileAttributes.FILE_ATTRIBUTE_COMPRESSED);
+ createOptionField(group, label, IWindowsFileAttributes.FILE_ATTRIBUTE_COMPRESSED, on);
+ }
+
+ /**
+ * Create an option field in the specified group, using the specified label,
+ * and with the specified boolean value.
+ *
+ * @param group
+ * The group widget where the field is created.
+ * @param label
+ * The label used by the field.
+ * @param bit
+ * The bit mask to be changed once the value is changed.
+ * @param on
+ * The boolean value to be set.
+ */
+ private void createOptionField(Group group, String label, final int bit, final boolean on) {
+ final Button button = new Button(group, SWT.CHECK);
+ button.setText(label);
+ button.setSelection(on);
+ button.addSelectionListener(new SelectionAdapter(){
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (button.getSelection() != on) {
+ node.setWin32Attr(bit, on);
+ }
+ }
+ });
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.window.Window#configureShell(org.eclipse.swt.widgets.Shell)
+ */
+ @Override
+ protected void configureShell(Shell newShell) {
+ super.configureShell(newShell);
+ newShell.setText(Messages.AdvancedAttributesDialog_ShellTitle);
+ }
+
+ /**
+ * Get the result.
+ * @return The result.
+ */
+ public FSTreeNode getResult() {
+ return node;
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/properties/GeneralInformationPage.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/properties/GeneralInformationPage.java
new file mode 100644
index 000000000..97210ce43
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/properties/GeneralInformationPage.java
@@ -0,0 +1,404 @@
+/*********************************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * William Chen (Wind River) - [345384]Provide property pages for remote file system nodes
+ * [361322]Minor improvements to the properties dialog of a file.
+ *********************************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.properties;
+
+import java.text.DateFormat;
+import java.text.DecimalFormat;
+import java.util.Date;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.core.runtime.ISafeRunnable;
+import org.eclipse.core.runtime.SafeRunner;
+import org.eclipse.core.runtime.content.IContentType;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.window.Window;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.tcf.te.tcf.filesystem.internal.handlers.ContentTypeHelper;
+import org.eclipse.tcf.te.tcf.filesystem.internal.handlers.StateManager;
+import org.eclipse.tcf.te.tcf.filesystem.internal.nls.Messages;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSTreeNode;
+import org.eclipse.ui.dialogs.PropertyPage;
+
+/**
+ * The general information page of a file's properties dialog.
+ */
+public class GeneralInformationPage extends PropertyPage {
+ // The formatter for the modified time and the accessed time.
+ private static final DateFormat DATE_FORMAT = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT);
+ // The formatter for the size of a file.
+ private static final DecimalFormat SIZE_FORMAT = new DecimalFormat();
+ // The original node.
+ FSTreeNode node;
+ // Cloned node for modification.
+ FSTreeNode clone;
+ // The button of "Read-Only"
+ Button btnReadOnly;
+ // The button of "Hidden"
+ Button btnHidden;
+ // The button of "Permissions"
+ Button[] btnPermissions;
+ /**
+ * Get the type of an FSTreeNode.
+ *
+ * @return "folder" if it is a directory, "file" if is a file, or else
+ * defaults to node.type.
+ */
+ protected String getNodeTypeLabel() {
+ if (clone.isDirectory())
+ return Messages.GeneralInformationPage_Folder;
+ else if (clone.isFile()) {
+ IContentType contentType = ContentTypeHelper.getInstance().getContentType(node);
+ String contentTypeName = contentType == null ? Messages.GeneralInformationPage_UnknownFileType : contentType.getName();
+ return NLS.bind(Messages.GeneralInformationPage_File, contentTypeName);
+ } else
+ return clone.type;
+ }
+
+ /**
+ * Create a horizontal separator between field sections.
+ *
+ * @param parent
+ * The parent composite of the separator.
+ */
+ protected void createSeparator(Composite parent) {
+ Label label = new Label(parent, SWT.SEPARATOR | SWT.SHADOW_ETCHED_IN | SWT.HORIZONTAL);
+ GridData data = new GridData(SWT.FILL, SWT.BEGINNING, true, false);
+ data.horizontalSpan = 2;
+ label.setLayoutData(data);
+ }
+
+ /**
+ * Create a field displaying the a specific value with a specific label.
+ *
+ * @param text
+ * The label text for the field.
+ * @param value
+ * The value to be displayed.
+ * @param parent
+ * The parent composite of the field.
+ */
+ protected void createField(String text, String value, Composite parent) {
+ Label label = new Label(parent, SWT.NONE);
+ label.setText(text);
+ GridData data = new GridData();
+ data.horizontalAlignment = SWT.LEFT;
+ data.verticalAlignment = SWT.TOP;
+ label.setLayoutData(data);
+ Text txt = new Text(parent, SWT.WRAP | SWT.READ_ONLY);
+ data = new GridData();
+ data.verticalAlignment = SWT.TOP;
+ data.widthHint = 300;
+ data.grabExcessHorizontalSpace = true;
+ data.horizontalAlignment = GridData.FILL;
+ txt.setLayoutData(data);
+ txt.setBackground(txt.getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
+ txt.setText(value);
+ }
+
+ /**
+ * Get the string of the file size using using the formatter, SIZE_FORMAT.
+ *
+ * @param size
+ * The size of the file to be formatted.
+ * @return The string in the format of SIZE_FORMAT.
+ */
+ protected String getSizeText(long size) {
+ return SIZE_FORMAT.format(size / 1024)
+ + " KB (" + SIZE_FORMAT.format(size) + " bytes)"; //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ /**
+ * Get the string of the specific time using the formatter, DATE_FORMAT.
+ *
+ * @param time
+ * The time to be formatted.
+ * @return The string in the format of DATE_FORMAT.
+ */
+ protected String getDateText(long time) {
+ return DATE_FORMAT.format(new Date(time));
+ }
+
+ /**
+ * Create the attributes section for a Windows file/folder.
+ *
+ * @param parent
+ * The parent composite on which it is created.
+ */
+ protected void createAttributesSection(Composite parent) {
+ // Attributes
+ Label label = new Label(parent, SWT.NONE);
+ label.setText(Messages.GeneralInformationPage_Attributes);
+ GridData data = new GridData();
+ data.horizontalAlignment = SWT.LEFT;
+ label.setLayoutData(data);
+
+ Composite attr = new Composite(parent, SWT.NONE);
+ GridLayout layout = new GridLayout(3, true);
+ layout.marginHeight = 0;
+ attr.setLayout(layout);
+ // Read-only
+ btnReadOnly = new Button(attr, SWT.CHECK);
+ btnReadOnly.setText(Messages.GeneralInformationPage_ReadOnly);
+ btnReadOnly.addSelectionListener(new SelectionAdapter(){
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if(btnReadOnly.getSelection()!=clone.isReadOnly()){
+ clone.setReadOnly(btnReadOnly.getSelection());
+ }
+ }
+ });
+ // Hidden
+ btnHidden = new Button(attr, SWT.CHECK);
+ btnHidden.setText(Messages.GeneralInformationPage_Hidden);
+ btnHidden.addSelectionListener(new SelectionAdapter(){
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ Button btnHidden = (Button) e.getSource();
+ if(btnHidden.getSelection()!=clone.isHidden()){
+ clone.setHidden(btnHidden.getSelection());
+ }
+ }
+ });
+ // Advanced Attributes
+ Button btnAdvanced = new Button(attr, SWT.PUSH);
+ btnAdvanced.setText(Messages.GeneralInformationPage_Advanced);
+ btnAdvanced.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ showAdvancedAttributes();
+ }
+ });
+ // Update the attribute values.
+ updateAttributes();
+ }
+
+ /**
+ * Update the value of attributes section.
+ */
+ private void updateAttributes() {
+ btnReadOnly.setSelection(clone.isReadOnly());
+ btnHidden.setSelection(clone.isHidden());
+ }
+
+ /**
+ * Show the advanced attributes dialog for the specified file/folder.
+ */
+ void showAdvancedAttributes() {
+ AdvancedAttributesDialog dialog = new AdvancedAttributesDialog(this.getShell(), (FSTreeNode)(clone.clone()));
+ if (dialog.open() == Window.OK) {
+ FSTreeNode result = dialog.getResult();
+ clone.attr = result.attr;
+ }
+ }
+
+ /**
+ * Create the permissions section for a Unix/Linux file/folder.
+ *
+ * @param parent
+ * The parent composite on which it is created.
+ */
+ protected void createPermissionsSection(Composite parent) {
+ GridLayout gridLayout;
+ Label label = new Label(parent, SWT.NONE);
+ label.setText("Permissions:"); //$NON-NLS-1$
+ GridData data = new GridData();
+ data.horizontalAlignment = SWT.LEFT;
+ data.verticalAlignment = SWT.TOP;
+ label.setLayoutData(data);
+ Composite perms = new Composite(parent, SWT.NONE);
+ gridLayout = new GridLayout(2, false);
+ gridLayout.marginHeight = 0;
+ perms.setLayout(gridLayout);
+ btnPermissions = new Button[9];
+ createPermissionGroup(perms, 0,
+ Messages.PermissionsGroup_UserPermissions);
+ createPermissionGroup(perms, 3,
+ Messages.PermissionsGroup_GroupPermissions);
+ createPermissionGroup(perms, 6,
+ Messages.PermissionsGroup_OtherPermissions);
+ // Update the permission values.
+ updatePermissions();
+ }
+
+ /**
+ * Create a permission group for a role, such as a user, a group or others.
+ *
+ * @param parent
+ * The parent composite.
+ * @param bit
+ * The permission bit index.
+ * @param header
+ * The group's header label.
+ */
+ protected void createPermissionGroup(Composite parent, int bit, String header) {
+ Label label = new Label(parent, SWT.NONE);
+ label.setText(header);
+ GridData data = new GridData();
+ data.horizontalAlignment = SWT.LEFT;
+ label.setLayoutData(data);
+ Composite group = new Composite(parent, SWT.NONE);
+ GridLayout layout = new GridLayout(3, true);
+ layout.marginHeight = 0;
+ group.setLayout(layout);
+ createPermissionButton(Messages.PermissionsGroup_Readable, bit, group);
+ createPermissionButton(Messages.PermissionsGroup_Writable, bit + 1, group);
+ createPermissionButton(Messages.PermissionsGroup_Executable, bit + 2, group);
+ }
+
+ /**
+ * Create a check-box field for a single permission item.
+ *
+ * @param label
+ * The label of the permission.
+ * @param index
+ * The index of current permission bit mask index.
+ * @param parent
+ * The parent to hold the check-box field.
+ */
+ private void createPermissionButton(String label, final int index, Composite parent) {
+ btnPermissions[index] = new Button(parent, SWT.CHECK);
+ btnPermissions[index].setText(label);
+ btnPermissions[index].addSelectionListener(new SelectionAdapter(){
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ int bit = 1 << (8 - index);
+ boolean on = (clone.attr.permissions & bit) != 0;
+ boolean newOn = btnPermissions[index].getSelection();
+ if (newOn != on) {
+ int permissions = clone.attr.permissions;
+ permissions = newOn ? (permissions | bit) : (permissions & ~bit);
+ clone.setPermissions(permissions);
+ }
+ }
+ });
+ }
+
+ /**
+ * Update the value of permissions section.
+ */
+ private void updatePermissions(){
+ for (int i = 0; i < 9; i++) {
+ final int bit = 1 << (8 - i);
+ final boolean on = (clone.attr.permissions & bit) != 0;
+ btnPermissions[i].setSelection(on);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.jface.preference.PreferencePage#performDefaults()
+ */
+ @Override
+ protected void performDefaults() {
+ clone = (FSTreeNode) node.clone();
+ if (node.isWindowsNode()) {
+ updateAttributes();
+ }
+ else {
+ updatePermissions();
+ }
+ super.performDefaults();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.jface.preference.PreferencePage#performOk()
+ */
+ @Override
+ public boolean performOk() {
+ if (hasAttrsChanged()) {
+ final boolean[] success = new boolean[1];
+ success[0] = true;
+ SafeRunner.run(new ISafeRunnable() {
+ @Override
+ public void handleException(Throwable exception) {
+ String errorMessage = NLS
+ .bind(Messages.GeneralInformationPage_PropertiesChangeFailure, new Object[] { node.name, exception
+ .getLocalizedMessage() });
+ MessageDialog.openError(getShell(), Messages.GeneralInformationPage_PropertiesChangeTitle, errorMessage);
+ success[0] = false;
+ }
+
+ @Override
+ public void run() throws Exception {
+ StateManager.getInstance().setFileAttrs(node, clone.attr);
+ }
+ });
+ return success[0];
+ }
+ return true;
+ }
+
+ /**
+ * If the attributes has been changed.
+ * @return If the attributes has been changed.
+ */
+ private boolean hasAttrsChanged(){
+ if(node.isWindowsNode()){
+ // If it is a Windows file, only check its attributes.
+ return node.getWin32Attrs() != clone.getWin32Attrs();
+ }
+ // If it is not a Windows file, only check its permissions.
+ return node.attr.permissions != clone.attr.permissions;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.preference.PreferencePage#createContents(org.eclipse.swt.widgets.Composite)
+ */
+ @Override
+ protected Control createContents(Composite parent) {
+ IAdaptable element = getElement();
+ Assert.isTrue(element instanceof FSTreeNode);
+
+ node = (FSTreeNode) element;
+ clone = (FSTreeNode) node.clone();
+ Composite page = new Composite(parent, SWT.NONE);
+ GridLayout gridLayout = new GridLayout(2, false);
+ page.setLayout(gridLayout);
+ // Field "Name"
+ createField(Messages.GeneralInformationPage_Name, clone.name, page);
+ // Field "Type"
+ createField(Messages.GeneralInformationPage_Type, getNodeTypeLabel(), page);
+ // Field "Location"
+ String location = clone.type.endsWith("FSRootNode") //$NON-NLS-1$
+ || clone.type.endsWith("FSRootDirNode") ? Messages.GeneralInformationPage_Computer //$NON-NLS-1$
+ : clone.getLocation();
+ createField(Messages.GeneralInformationPage_Location, location, page);
+ // Field "Size"
+ if (clone.isFile()) {
+ createField(Messages.GeneralInformationPage_Size, getSizeText(clone.attr.size), page);
+ }
+ // Field "Modified"
+ createField(Messages.GeneralInformationPage_Modified, getDateText(clone.attr.mtime), page);
+ // Field "Accessed"
+ if (clone.isFile()) {
+ createField(Messages.GeneralInformationPage_Accessed, getDateText(clone.attr.atime), page);
+ }
+ createSeparator(page);
+ if (clone.isWindowsNode()) {
+ createAttributesSection(page);
+ } else {
+ createPermissionsSection(page);
+ }
+ return page;
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/url/Rendezvous.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/url/Rendezvous.java
new file mode 100644
index 000000000..56eeb103e
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/url/Rendezvous.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * William Chen (Wind River)- [345387]Open the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.url;
+
+/**
+ * A helper class used to synchronize producer and consumer threads. It is used
+ * to join a thread with its asynchronous call backs or listeners. Usually it is
+ * used in a case in which the thread have to wait for the end of an
+ * asynchronously called method.
+ * <p>
+ * The following is an example:
+ * <p>
+ *
+ * <pre>
+ * final Rendezvous rendezvous = new Rendezvous();
+ * service.open(path, IFileSystem.TCF_O_READ, null, new DoneOpen() {
+ * public void doneOpen(IToken token, FileSystemException error, IFileHandle hdl) {
+ * ...
+ * rendezvous.arrive();
+ * }
+ * });
+ * try{
+ * renderzvous.waiting(1000); //Waiting for 1 second.
+ * }catch(InterruptedException e){
+ * // Waiting has timed out.
+ * ...
+ * }
+ * </pre>
+ *
+ * The call renderzvous.waiting(1000) won't return until renderzvous.arrive() is
+ * called in the doneOpen(), or the waiting has timed out.
+ * <p>
+ * A rendezvous can be reused once it is reset:
+ * <p>
+ *
+ * <pre>
+ * renderzvous.reset();
+ * service.open(path, IFileSystem.TCF_O_READ, null, new DoneOpen() {
+ * public void doneOpen(IToken token, FileSystemException error, IFileHandle hdl) {
+ * ...
+ * rendezvous.arrive();
+ * }
+ * });
+ * try{
+ * renderzvous.waiting(2000); //Waiting for 2 seconds.
+ * }catch(InterruptedException e){
+ * // Waiting has timed out.
+ * ...
+ * }
+ * </pre>
+ *
+ */
+public class Rendezvous {
+ // Flag indicating if the other thread has arrived.
+ private boolean arrived;
+
+ /**
+ * Called to unblock the thread that is waiting on this rendezvous.
+ */
+ public synchronized void arrive() {
+ arrived = true;
+ notifyAll();
+ }
+
+ /**
+ * Called to block the current thread until it is woken up by
+ * another thread or until it is timed out.
+ *
+ * @param timeout The timeout time.
+ * @throws InterruptedException The waiting has timed out.
+ */
+ public synchronized void waiting(long timeout) throws InterruptedException {
+ long now = System.currentTimeMillis();
+ while (!arrived && (timeout <= 0 || System.currentTimeMillis() - now < timeout)) {
+ try {
+ wait(timeout);
+ } catch (InterruptedException e) {
+ }
+ }
+ if (!arrived)
+ throw new InterruptedException();
+ }
+
+ /**
+ * Called to block the current thread until it is woken up by another
+ * thread.
+ *
+ * @throws InterruptedException The waiting has timed out.
+ */
+ public synchronized void waiting() throws InterruptedException {
+ waiting(0);
+ }
+
+ /**
+ * Reset the rendezvous so that it is reusable.
+ */
+ public synchronized void reset() {
+ arrived = false;
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/url/TcfInputStream.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/url/TcfInputStream.java
new file mode 100644
index 000000000..b8753067e
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/url/TcfInputStream.java
@@ -0,0 +1,172 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * William Chen (Wind River)- [345387]Open the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.url;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.tcf.protocol.IToken;
+import org.eclipse.tcf.services.IFileSystem;
+import org.eclipse.tcf.services.IFileSystem.DoneRead;
+import org.eclipse.tcf.services.IFileSystem.FileSystemException;
+import org.eclipse.tcf.te.tcf.filesystem.internal.nls.Messages;
+
+/**
+ * The TCF input stream returned by {@link TcfURLConnection#getInputStream()}.
+ *
+ */
+public class TcfInputStream extends InputStream {
+ // Default chunk size while pumping the data.
+ private static final int DEFAULT_CHUNK_SIZE = 5 * 1024;
+
+ // Current reading position
+ long position;
+ // The byte array used to buffer data.
+ byte[] buffer;
+ // The offset being read in the buffer.
+ int offset;
+
+ // If the reading has reached the end of the file.
+ boolean EOF;
+ // If the stream has been closed.
+ boolean closed;
+ // The current error during reading.
+ Exception ERROR;
+
+ // The chunk size of the reading buffer.
+ int chunk_size = 0;
+
+ // File reading timeout.
+ int timeout;
+
+ // The URL Connection
+ TcfURLConnection connection;
+
+ /**
+ * Create a TCF input stream connected the specified peer with specified
+ * path to the remote resource.
+ *
+ * @param peer
+ * The TCF agent peer.
+ * @param path
+ * The path to the remote resource.
+ */
+ public TcfInputStream(TcfURLConnection connection) {
+ this(connection, DEFAULT_CHUNK_SIZE);
+ }
+
+ /**
+ * Create a TCF input stream connected the specified peer with specified
+ * path to the remote resource using the specified buffer size.
+ *
+ * @param peer
+ * The TCF agent peer.
+ * @param path
+ * The path to the remote resource.
+ * @param chunk_size
+ * The buffer size.
+ */
+ public TcfInputStream(TcfURLConnection connection, int chunk_size) {
+ this.connection = connection;
+ this.chunk_size = chunk_size;
+ }
+
+ /**
+ * Set the timeout for reading a file.
+ *
+ * @param timeout the timeout in milliseconds.
+ */
+ void setTimeout(int readTimeout) {
+ this.timeout = readTimeout;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.io.InputStream#read()
+ */
+ @Override
+ public int read() throws IOException {
+ if (closed)
+ throw new IOException(Messages.TcfInputStream_StreamClosed);
+ if (ERROR != null) {
+ IOException exception = new IOException(ERROR.toString());
+ exception.initCause(ERROR);
+ throw exception;
+ }
+ if (buffer == null) {
+ if (EOF) {
+ return -1;
+ }
+ readBlock();
+ return read();
+ }
+ if (EOF) {
+ if (offset == buffer.length) {
+ return -1;
+ }
+ // Note that convert the byte to an integer correctly
+ return 0xff & buffer[offset++];
+ }
+ if (offset == buffer.length) {
+ readBlock();
+ return read();
+ }
+ // Note that convert the byte to an integer correctly
+ return 0xff & buffer[offset++];
+ }
+
+ /**
+ * Read a block of data into the buffer. Reset the offset, increase the
+ * current position and remember the EOF status. If there's an error,
+ * remember it for read() to check.
+ */
+ private void readBlock() {
+ final Rendezvous rendezvous = new Rendezvous();
+ IFileSystem service = connection.handle.getService();
+ service.read(connection.handle, position, chunk_size, new DoneRead() {
+ @Override
+ public void doneRead(IToken token, FileSystemException error, byte[] data, boolean eof) {
+ if (error != null) {
+ ERROR = error;
+ }
+ if (data == null) {
+ ERROR = new IOException(Messages.TcfInputStream_NoDataAvailable);
+ }
+ EOF = eof;
+ buffer = data;
+ if (buffer != null)
+ position += buffer.length;
+ offset = 0;
+ // Rendezvous
+ rendezvous.arrive();
+ }
+ });
+ // Waiting for reading.
+ try {
+ rendezvous.waiting(timeout);
+ } catch (InterruptedException e) {
+ ERROR = new IOException(Messages.TcfInputStream_ReadTimeout);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.io.InputStream#close()
+ */
+ @Override
+ public void close() throws IOException {
+ if (!closed) {
+ connection.closeStream(this);
+ closed = true;
+ }
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/url/TcfOutputStream.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/url/TcfOutputStream.java
new file mode 100644
index 000000000..a03f2124d
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/url/TcfOutputStream.java
@@ -0,0 +1,146 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * William Chen (Wind River) - [345552] Edit the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.url;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.eclipse.tcf.protocol.IToken;
+import org.eclipse.tcf.services.IFileSystem;
+import org.eclipse.tcf.services.IFileSystem.DoneWrite;
+import org.eclipse.tcf.services.IFileSystem.FileSystemException;
+import org.eclipse.tcf.te.tcf.filesystem.internal.nls.Messages;
+
+/**
+ * The TCF output stream returned by {@link TcfURLConnection#getOutputStream()}.
+ *
+ */
+public class TcfOutputStream extends OutputStream {
+ // Default chunk size while pumping the data.
+ private static final int DEFAULT_CHUNK_SIZE = 5 * 1024;
+
+ // Current writing position
+ long position;
+ // The byte array used to buffer data.
+ byte[] buffer;
+ // The offset being written in the buffer.
+ int offset;
+
+ // If the stream has been closed.
+ boolean closed;
+ // The current error during writing.
+ Exception ERROR;
+
+ // The chunk size of the writing buffer.
+ int chunk_size = 0;
+
+ // The URL Connection
+ TcfURLConnection connection;
+
+ // The timeout for writing data.
+ int timeout;
+ /**
+ * Create a TCF output stream connected the specified peer with specified
+ * path to the remote resource.
+ *
+ * @param peer
+ * The TCF agent peer.
+ * @param path
+ * The path to the remote resource.
+ */
+ public TcfOutputStream(TcfURLConnection connection) {
+ this(connection, DEFAULT_CHUNK_SIZE);
+ }
+
+ /**
+ * Create a TCF output stream connected the specified peer with specified
+ * path to the remote resource using the specified buffer size.
+ *
+ * @param peer
+ * The TCF agent peer.
+ * @param path
+ * The path to the remote resource.
+ * @param chunk_size
+ * The buffer size.
+ */
+ public TcfOutputStream(TcfURLConnection connection, int chunk_size) {
+ this.connection = connection;
+ this.chunk_size = chunk_size;
+ buffer = new byte[chunk_size];
+ offset = 0;
+ }
+
+ /**
+ * Set the timeout for writing a file.
+ *
+ * @param timeout The timeout for writing a file.
+ */
+ void setTimeout(int timeout){
+ this.timeout = timeout;
+ }
+
+ /* (non-Javadoc)
+ * @see java.io.OutputStream#write(int)
+ */
+ @Override
+ public void write(int b) throws IOException {
+ if (closed)
+ throw new IOException(Messages.TcfOutputStream_StreamClosed);
+ if (ERROR != null) {
+ IOException exception = new IOException(ERROR.toString());
+ exception.initCause(ERROR);
+ throw exception;
+ }
+ if (offset < buffer.length) {
+ buffer[offset++] = (byte) b;
+ }
+ if (offset == buffer.length)
+ flush();
+ }
+
+ /* (non-Javadoc)
+ * @see java.io.OutputStream#flush()
+ */
+ @Override
+ public void flush() throws IOException {
+ if (offset > 0) {
+ final Rendezvous rendezvous = new Rendezvous();
+ IFileSystem service = connection.handle.getService();
+ service.write(connection.handle, position, buffer, 0, offset, new DoneWrite() {
+ @Override
+ public void doneWrite(IToken token, FileSystemException error) {
+ if (error != null) {
+ ERROR = error;
+ }
+ position += offset;
+ offset = 0;
+ rendezvous.arrive();
+ }
+ });
+ // Waiting for writing.
+ try {
+ rendezvous.waiting(timeout);
+ } catch (InterruptedException e) {
+ ERROR = new IOException(Messages.TcfOutputStream_WriteTimeout);
+ }
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see java.io.OutputStream#close()
+ */
+ @Override
+ public void close() throws IOException {
+ if (!closed) {
+ connection.closeStream(this);
+ closed = true;
+ }
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/url/TcfURLConnection.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/url/TcfURLConnection.java
new file mode 100644
index 000000000..39403670e
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem/src/org/eclipse/tcf/te/tcf/filesystem/internal/url/TcfURLConnection.java
@@ -0,0 +1,325 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems, Inc. and others. 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:
+ * William Chen (Wind River)- [345387]Open the remote files with a proper editor
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.internal.url;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.net.URLConnection;
+
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.tcf.protocol.IChannel;
+import org.eclipse.tcf.protocol.IPeer;
+import org.eclipse.tcf.protocol.IToken;
+import org.eclipse.tcf.services.IFileSystem;
+import org.eclipse.tcf.services.IFileSystem.DoneClose;
+import org.eclipse.tcf.services.IFileSystem.DoneOpen;
+import org.eclipse.tcf.services.IFileSystem.FileSystemException;
+import org.eclipse.tcf.services.IFileSystem.IFileHandle;
+import org.eclipse.tcf.te.tcf.filesystem.internal.exceptions.TCFChannelException;
+import org.eclipse.tcf.te.tcf.filesystem.internal.nls.Messages;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSModel;
+import org.eclipse.tcf.te.tcf.filesystem.model.FSTreeNode;
+import org.eclipse.tcf.te.tcf.core.Tcf;
+import org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager.DoneOpenChannel;
+
+/**
+ * The URL connection returned by TCF stream service used to handler "tcf"
+ * stream protocol.
+ */
+public class TcfURLConnection extends URLConnection {
+ // Default connecting timeout.
+ private static final int DEFAULT_CONNECT_TIMEOUT = 5000;
+ // Default file opening timeout.
+ private static final int DEFAULT_OPEN_TIMEOUT = 5000;
+ // Default file reading timeout.
+ private static final int DEFAULT_READ_TIMEOUT = 5000;
+ // Default file closing timeout.
+ private static final int DEFAULT_CLOSE_TIMEOUT = 5000;
+ // The schema name of the stream protocol.
+ public static final String PROTOCOL_SCHEMA = "tcf"; //$NON-NLS-1$
+
+ // The input stream of this connection.
+ private TcfInputStream inputStream;
+ // The output stream of this connection.
+ private TcfOutputStream outputStream;
+
+ // The TCF agent peer of the connection.
+ private IPeer peer;
+ // The path to the resource on the remote file system.
+ private String path;
+ // The timeout for opening a file.
+ private int openTimeout;
+ // The timeout for closing a file.
+ private int closeTimeout;
+
+ // The TCF channel used to open and read the resource.
+ IChannel channel;
+ // The file's handle
+ IFileHandle handle;
+
+ /**
+ * Create a TCF URL Connection using the specified url. The format of this
+ * URL should be: tcf:///<TCF_AGENT_ID>/remote/path/to/the/resource... The
+ * stream protocol schema is designed in this way in order to retrieve the
+ * agent peer ID without knowing the structure of a TCF peer id.
+ *
+ * @param url
+ * The URL of the resource.
+ */
+ public TcfURLConnection(URL url) {
+ super(url);
+ // The path should have already contained the peer's id like:
+ // /<TCF_AGENT_ID>/remote/path/to/the/resource...
+ path = url.getPath();
+ int slash = path.indexOf("/", 1); //$NON-NLS-1$
+ if (slash != -1){
+ path = path.substring(slash);
+ if (path.matches("/[A-Za-z]:.*")) path = path.substring(1); //$NON-NLS-1$
+ }
+ //Get the peer using the peer id.
+ FSTreeNode node = FSModel.getInstance().getTreeNode(url);
+ if(node != null)
+ peer = node.peerNode.getPeer();
+ // Set default timeout.
+ setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
+ setOpenTimeout(DEFAULT_OPEN_TIMEOUT);
+ setReadTimeout(DEFAULT_READ_TIMEOUT);
+ setCloseTimeout(DEFAULT_CLOSE_TIMEOUT);
+ }
+
+ /**
+ * Get the timeout for closing a file.
+ *
+ * @return the timeout in milliseconds.
+ */
+ public long getCloseTimeout() {
+ return closeTimeout;
+ }
+
+ /**
+ * Set the timeout for closing a file.
+ *
+ * @param closeTimeout
+ * the timeout in milliseconds.
+ */
+ public void setCloseTimeout(int closeTimeout) {
+ this.closeTimeout = closeTimeout;
+ }
+
+ /**
+ * Get the timeout for opening a file.
+ *
+ * @return the timeout in milliseconds.
+ */
+ public long getOpenTimeout() {
+ return openTimeout;
+ }
+
+ /**
+ * Set the timeout for opening a file.
+ *
+ * @param openTimeout
+ * the timeout in milliseconds.
+ */
+ public void setOpenTimeout(int openTimeout) {
+ this.openTimeout = openTimeout;
+ }
+
+ /**
+ * Open a channel connected to the target represented by the peer.
+ *
+ * @return The channel or null if the operation fails.
+ */
+ private IChannel openChannel(final IPeer peer) throws TCFChannelException {
+ final Rendezvous rendezvous = new Rendezvous();
+ final TCFChannelException[] errors = new TCFChannelException[1];
+ final IChannel[] channels = new IChannel[1];
+ Tcf.getChannelManager().openChannel(peer, new DoneOpenChannel(){
+ @Override
+ public void doneOpenChannel(Throwable error, IChannel channel) {
+ if(error!=null){
+ String message = NLS.bind(Messages.TCFUtilities_OpeningFailureMessage,
+ new Object[]{peer.getID(), error.getLocalizedMessage()});
+ errors[0] = new TCFChannelException(message, error);
+ }else{
+ channels[0] = channel;
+ }
+ rendezvous.arrive();
+ }});
+ try {
+ rendezvous.waiting(5000L);
+ } catch (InterruptedException e) {
+ String message = NLS.bind(Messages.TCFUtilities_OpeningFailureMessage,
+ new Object[]{peer.getID(), e.getLocalizedMessage()});
+ errors[0] = new TCFChannelException(message, e);
+ }
+ if(errors[0] != null){
+ throw errors[0];
+ }
+ return channels[0];
+ }
+
+ /**
+ * Open a file on the remote file system for read/write and store the file handle.
+ *
+ * @throws IOException Opening file fails.
+ */
+ private void openFile() throws IOException {
+ if(peer == null)
+ throw new IOException(Messages.TcfURLConnection_NoSuchTcfAgent);
+ try {
+ // Open the channel
+ channel = openChannel(peer);
+ } catch (TCFChannelException e) {
+ throw new IOException(e.getLocalizedMessage());
+ }
+ if (channel != null) {
+ IFileSystem service = channel.getRemoteService(IFileSystem.class);
+ if (service != null) {
+ final Rendezvous rendezvous = new Rendezvous();
+ final FileSystemException[] errors = new FileSystemException[1];
+ // Open the file.
+ int open_flag = 0;
+ if (doInput)
+ open_flag |= IFileSystem.TCF_O_READ;
+ if (doOutput)
+ open_flag |= IFileSystem.TCF_O_WRITE;
+ service.open(path, open_flag, null, new DoneOpen() {
+ @Override
+ public void doneOpen(IToken token, FileSystemException error, IFileHandle hdl) {
+ errors[0] = error;
+ handle = hdl;
+ // Rendezvous
+ rendezvous.arrive();
+ }
+ });
+ try {
+ rendezvous.waiting(openTimeout);
+ } catch (InterruptedException e) {
+ throw new IOException(Messages.TcfURLConnection_OpenFileTimeout);
+ }
+ if (errors[0] != null) {
+ IOException exception = new IOException(errors[0].toString());
+ exception.initCause(errors[0]);
+ throw exception;
+ }
+ if (handle == null) {
+ throw new IOException(Messages.TcfURLConnection_NoFileHandleReturned);
+ }
+ } else {
+ throw new IOException(Messages.TcfURLConnection_NoFSServiceAvailable);
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.net.URLConnection#connect()
+ */
+ @Override
+ public void connect() throws IOException {
+ if (!connected) {
+ openFile();
+ if (doInput) {
+ inputStream = new TcfInputStream(this);
+ inputStream.setTimeout(getReadTimeout());
+ }
+ if (doOutput) {
+ outputStream = new TcfOutputStream(this);
+ outputStream.setTimeout(getReadTimeout());
+ }
+ connected = true;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.net.URLConnection#getInputStream()
+ */
+ @Override
+ public InputStream getInputStream() throws IOException {
+ if (!connected)
+ connect();
+ return inputStream;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.net.URLConnection#getOutputStream()
+ */
+ @Override
+ public OutputStream getOutputStream() throws IOException {
+ if (!connected)
+ connect();
+ return outputStream;
+ }
+
+ /**
+ * Close the stream, release its file handler and close
+ * the TCF channel used if possible.
+ *
+ * @param stream The stream either the input stream or the output stream.
+ * @throws IOException If closing file handle times out.
+ */
+ public synchronized void closeStream(Closeable stream) throws IOException {
+ boolean shouldClose = shouldCloseFileHandle(stream);
+ if (shouldClose) {
+ final Rendezvous rendezvous = new Rendezvous();
+ IFileSystem service = handle.getService();
+ service.close(handle, new DoneClose() {
+ @Override
+ public void doneClose(IToken token, FileSystemException error) {
+ rendezvous.arrive();
+ }
+ });
+ try {
+ rendezvous.waiting(closeTimeout);
+ } catch (InterruptedException e) {
+ throw new IOException(Messages.TcfURLConnection_CloseFileTimeout);
+ }
+ channel.close();
+ }
+ }
+
+ /**
+ * Decide if the file handle and the TCF channel should be closed if
+ * the specified stream is closed. If the stream is the last stream
+ * that depends on the file handle and the TCF channel, then it should
+ * be closed.
+ *
+ * @param stream The stream to be closed.
+ * @return true if the file handle and the TCF channel should be closed.
+ */
+ private boolean shouldCloseFileHandle(Closeable stream) {
+ boolean shouldClose = false;
+ if (stream == inputStream) {
+ if (doOutput) {
+ if (outputStream.closed) {
+ shouldClose = true;
+ }
+ } else {
+ shouldClose = true;
+ }
+ } else if (stream == outputStream) {
+ if (doInput) {
+ if (inputStream.closed)
+ shouldClose = true;
+ } else {
+ shouldClose = true;
+ }
+ }
+ return shouldClose;
+ }
+}

Back to the top