/*******************************************************************************
* 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.tm.te.tcf.filesystem.controls;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.widgets.Display;
import org.eclipse.tm.tcf.protocol.IChannel;
import org.eclipse.tm.tcf.protocol.IPeer;
import org.eclipse.tm.tcf.protocol.IToken;
import org.eclipse.tm.tcf.protocol.Protocol;
import org.eclipse.tm.tcf.services.IFileSystem;
import org.eclipse.tm.tcf.services.IFileSystem.DirEntry;
import org.eclipse.tm.tcf.services.IFileSystem.FileSystemException;
import org.eclipse.tm.tcf.services.IFileSystem.IFileHandle;
import org.eclipse.tm.te.tcf.core.Tcf;
import org.eclipse.tm.te.tcf.core.interfaces.IChannelManager;
import org.eclipse.tm.te.tcf.filesystem.internal.events.INodeStateListener;
import org.eclipse.tm.te.tcf.filesystem.model.FSModel;
import org.eclipse.tm.te.tcf.filesystem.model.FSTreeNode;
import org.eclipse.tm.te.tcf.locator.interfaces.nodes.IPeerModel;
import org.eclipse.tm.te.tcf.locator.interfaces.nodes.IPeerModelProperties;
import org.eclipse.tm.te.ui.nls.Messages;
import org.eclipse.ui.PlatformUI;
/**
* File system tree content provider implementation.
*/
public class FSTreeContentProvider implements ITreeContentProvider, INodeStateListener {
/**
* Static reference to the return value representing no elements.
*/
protected final static Object[] NO_ELEMENTS = new Object[0];
/**
* The file system model instance associated with this file system
* tree content provider instance.
*/
/* package */ final static FSModel model = FSModel.getInstance();
/* package */ TreeViewer viewer = null;
public FSTreeContentProvider(){
model.addNodeStateListener(this);
}
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer, java.lang.Object, java.lang.Object)
*/
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
this.viewer = (TreeViewer) viewer;
}
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.IContentProvider#dispose()
*/
@Override
public void dispose() {
model.removeNodeStateListener(this);
}
/**
* Close the open communication channel.
*/
protected void closeOpenChannel(final IChannel channel) {
if (channel != null) {
if (Protocol.isDispatchThread()) {
channel.close();
} else {
Protocol.invokeAndWait(new Runnable() {
@Override
public void run() {
channel.close();
}
});
}
}
}
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.ITreeContentProvider#getElements(java.lang.Object)
*/
@Override
public Object[] getElements(Object inputElement) {
return getChildren(inputElement);
}
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.ITreeContentProvider#getParent(java.lang.Object)
*/
@Override
public Object getParent(Object element) {
if (element instanceof FSTreeNode) {
FSTreeNode parent = ((FSTreeNode)element).parent;
// If the parent is a root node, return the associated peer node
if (parent != null && parent.type != null && parent.type.endsWith("RootNode")) { //$NON-NLS-1$
return parent.peerNode;
}
return parent;
}
return null;
}
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.ITreeContentProvider#getChildren(java.lang.Object)
*/
@Override
public Object[] getChildren(Object parentElement) {
Assert.isNotNull(parentElement);
Object[] children = NO_ELEMENTS;
// For the file system, we need the peer node
if (parentElement instanceof IPeerModel) {
final IPeerModel peerNode = (IPeerModel)parentElement;
final String peerId = peerNode.getPeer().getID();
// Get the file system model root node, if already stored
final FSTreeNode[] root = new FSTreeNode[1];
if (Protocol.isDispatchThread()) {
root[0] = model.getRoot(peerId);
} else {
Protocol.invokeAndWait(new Runnable() {
@Override
public void run() {
root[0] = model.getRoot(peerId);
}
});
}
// If the file system model root node hasn't been created, create
// and initialize the root node now.
if (root[0] == null) {
IPeer peer = peerNode.getPeer();
final int[] state = new int[1];
Protocol.invokeAndWait(new Runnable() {
@Override
public void run() {
state[0] = peerNode.getIntProperty(IPeerModelProperties.PROP_STATE);
}
});
if (peer != null && IPeerModelProperties.STATE_ERROR != state[0] && IPeerModelProperties.STATE_NOT_REACHABLE != state[0]) {
final List<FSTreeNode> candidates = new ArrayList<FSTreeNode>();
// Create the root node and the initial pending node.
// This must happen in the TCF dispatch thread.
Protocol.invokeAndWait(new Runnable() {
@Override
public void run() {
// The root node
FSTreeNode rootNode = new FSTreeNode();
rootNode.type = "FSRootNode"; //$NON-NLS-1$
rootNode.peerNode = peerNode;
rootNode.childrenQueried = false;
rootNode.childrenQueryRunning = true;
model.putRoot(peerId, rootNode);
// Add a special "Pending..." node
FSTreeNode pendingNode = new FSTreeNode();
pendingNode.name = Messages.PendingOperation_label;
pendingNode.type ="FSPendingNode"; //$NON-NLS-1$
pendingNode.parent = rootNode;
pendingNode.peerNode = rootNode.peerNode;
rootNode.getChildren().add(pendingNode);
candidates.addAll(rootNode.getChildren());
}
});
children = candidates.toArray();
Tcf.getChannelManager().openChannel(peer, new IChannelManager.DoneOpenChannel() {
@Override
public void doneOpenChannel(final Throwable error, final IChannel channel) {
Assert.isTrue(Protocol.isDispatchThread());
if (channel != null) {
final IFileSystem service = channel.getRemoteService(IFileSystem.class);
if (service != null) {
Protocol.invokeLater(new Runnable() {
@Override
public void run() {
service.roots(new IFileSystem.DoneRoots() {
@Override
public void doneRoots(IToken token, FileSystemException error, DirEntry[] entries) {
// Close the channel, not needed anymore
closeOpenChannel(channel);
FSTreeNode rootNode = model.getRoot(peerId);
if (rootNode != null && error == null) {
for (DirEntry entry : entries) {
FSTreeNode node = createNodeFromDirEntry(entry, true);
if (node != null) {
node.parent = rootNode;
node.peerNode = rootNode.peerNode;
rootNode.getChildren().add(node);
model.addNode(node);
}
}
// Find the pending node and remove it from the child list
Iterator<FSTreeNode> iterator = rootNode.getChildren().iterator();
while (iterator.hasNext()) {
FSTreeNode candidate = iterator.next();
if (Messages.PendingOperation_label.equals(candidate.name)) {
iterator.remove();
break;
}
}
// Reset the children query markers
rootNode.childrenQueryRunning = false;
rootNode.childrenQueried = true;
}
PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
if (viewer != null) viewer.refresh();
}
});
}
});
}
});
PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
if (viewer != null) viewer.refresh();
}
});
} else {
// The file system service is not available for this peer.
// --> Close the just opened channel
closeOpenChannel(channel);
}
}
}
});
}
} else {
// Get possible children
// This must happen in the TCF dispatch thread.
final List<FSTreeNode> candidates = new ArrayList<FSTreeNode>();
Protocol.invokeAndWait(new Runnable() {
@Override
public void run() {
candidates.addAll(root[0].getChildren());
}
});
children = candidates.toArray();
}
} else if (parentElement instanceof FSTreeNode) {
final FSTreeNode node = (FSTreeNode)parentElement;
// Get possible children
// This must happen in the TCF dispatch thread.
final List<FSTreeNode> candidates = new ArrayList<FSTreeNode>();
Protocol.invokeAndWait(new Runnable() {
@Override
public void run() {
candidates.addAll(node.getChildren());
}
});
children = candidates.toArray();
// No children -> check for "childrenQueried" property. If false, trigger the query.
if (children.length == 0 && !node.childrenQueried && node.type.endsWith("DirNode")) { //$NON-NLS-1$
candidates.clear();
// Add a special "Pending..." node
// This must happen in the TCF dispatch thread.
Protocol.invokeAndWait(new Runnable() {
@Override
public void run() {
FSTreeNode pendingNode = new FSTreeNode();
pendingNode.name = Messages.PendingOperation_label;
pendingNode.type ="FSPendingNode"; //$NON-NLS-1$
pendingNode.parent = node;
pendingNode.peerNode = node.peerNode;
node.getChildren().add(pendingNode);
candidates.addAll(node.getChildren());
}
});
children = candidates.toArray();
if (!node.childrenQueryRunning && node.peerNode != null) {
node.childrenQueryRunning = true;
final String absName = getEntryAbsoluteName(node);
if (absName != null) {
// Open a channel to the peer and query the children
Tcf.getChannelManager().openChannel(node.peerNode.getPeer(), new IChannelManager.DoneOpenChannel() {
@Override
public void doneOpenChannel(final Throwable error, final IChannel channel) {
Assert.isTrue(Protocol.isDispatchThread());
if (channel != null && channel.getState() == IChannel.STATE_OPEN) {
final IFileSystem service = channel.getRemoteService(IFileSystem.class);
if (service != null) {
Protocol.invokeLater(new Runnable() {
@Override
public void run() {
service.opendir(absName, new IFileSystem.DoneOpen() {
@Override
public void doneOpen(IToken token, FileSystemException error, final IFileHandle handle) {
if (error == null) {
// Read the directory content until finished
readdir(channel, service, handle, node);
} else {
// In case of an error, we are done here
node.childrenQueryRunning = false;
node.childrenQueried = true;
}
}
});
}
});
} else {
// No file system service available
node.childrenQueryRunning = false;
node.childrenQueried = true;
}
} else {
// Channel failed to open
node.childrenQueryRunning = false;
node.childrenQueried = true;
}
}
});
} else {
// No absolute name
node.childrenQueryRunning = false;
node.childrenQueried = true;
}
}
}
}
else {
// If the node can be adapted to an IPeerModel object.
Object adapted = adaptPeerModel(parentElement);
if (adapted != null) {
children = getChildren(adapted);
}
}
return children;
}
/**
* Adapt the specified element to a IPeerModel.
*
* @param element The element to be adapted.
* @return The IPeerModel adapted.
*/
private Object adaptPeerModel(Object element) {
Object adapted;
if (element instanceof IAdaptable) {
adapted = ((IAdaptable) element).getAdapter(IPeerModel.class);
}
else {
adapted = Platform.getAdapterManager().getAdapter(element, IPeerModel.class);
}
return adapted;
}
/**
* Reads the content of a directory until the file system service signals EOF.
*
* @param channel The open channel. Must not be <code>null</code>.
* @param service The file system service. Must not be <code>null</code>.
* @param handle The directory handle. Must not be <code>null</code>.
* @param parentNode The parent node receiving the entries. Must not be <code>null</code>.
* @param mode The notification mode to set to the parent node once done.
*/
protected void readdir(final IChannel channel, final IFileSystem service, final IFileHandle handle, final FSTreeNode parentNode) {
Assert.isNotNull(channel);
Assert.isNotNull(service);
Assert.isNotNull(handle);
Assert.isNotNull(parentNode);
Protocol.invokeLater(new Runnable() {
@Override
public void run() {
service.readdir(handle, new IFileSystem.DoneReadDir() {
@Override
public void doneReadDir(IToken token, FileSystemException error, DirEntry[] entries, boolean eof) {
// Close the handle and channel if EOF is signaled or an error occurred.
if (eof) {
service.close(handle, new IFileSystem.DoneClose() {
@Override
public void doneClose(IToken token, FileSystemException error) {
closeOpenChannel(channel);
}
});
}
// Process the returned data
if (error == null && entries != null && entries.length > 0) {
for (DirEntry entry : entries) {
FSTreeNode node = createNodeFromDirEntry(entry, false);
if (node != null) {
node.parent = parentNode;
node.peerNode = parentNode.peerNode;
parentNode.getChildren().add(node);
model.addNode(node);
}
}
}
if (eof) {
// Find the pending node and remove it from the child list
Iterator<FSTreeNode> iterator = parentNode.getChildren().iterator();
while (iterator.hasNext()) {
FSTreeNode candidate = iterator.next();
if (Messages.PendingOperation_label.equals(candidate.name)) {
iterator.remove();
break;
}
}
// Reset the children query markers
parentNode.childrenQueryRunning = false;
parentNode.childrenQueried = true;
} else {
// And invoke ourself again
readdir(channel, service, handle, parentNode);
}
PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
viewer.refresh(parentNode);
}
});
}
});
}
});
}
/**
* Creates a tree node from the given directory entry.
*
* @param entry The directory entry. Must not be <code>null</code>.
*
* @return The tree node.
*/
protected FSTreeNode createNodeFromDirEntry(DirEntry entry, boolean entryIsRootNode) {
Assert.isNotNull(entry);
FSTreeNode node = null;
IFileSystem.FileAttrs attrs = entry.attrs;
if (attrs == null || attrs.isDirectory()) {
node = new FSTreeNode();
node.childrenQueried = false;
node.childrenQueryRunning = false;
node.attr = attrs;
node.name = entry.filename;
node.type = entryIsRootNode ? "FSRootDirNode" : "FSDirNode"; //$NON-NLS-1$ //$NON-NLS-2$
} else if (attrs.isFile()) {
node = new FSTreeNode();
node.childrenQueried = false;
node.childrenQueryRunning = false;
node.attr = attrs;
node.name = entry.filename;
node.type = "FSFileNode"; //$NON-NLS-1$
}
return node;
}
/**
* Returns the absolute name for the given node.
*
* @param node The node. Must not be <code>null</code>.
* @return The absolute name.
*/
public static String getEntryAbsoluteName(FSTreeNode node) {
Assert.isNotNull(node);
StringBuilder path = new StringBuilder();
// We have to walk upwards the hierarchy until the root node is found
FSTreeNode parent = node.parent;
while (parent != null && parent.type != null && parent.type.startsWith("FS")) { //$NON-NLS-1$
if ("FSRootNode".equals(parent.type)) { //$NON-NLS-1$
// We are done if reaching the root node
break;
}
if (path.length() == 0) path.append(parent.name.replaceAll("\\\\", "/")); //$NON-NLS-1$ //$NON-NLS-2$
else {
String name = parent.name.replaceAll("\\\\", "/"); //$NON-NLS-1$ //$NON-NLS-2$
if (!name.endsWith("/")) name = name + "/"; //$NON-NLS-1$ //$NON-NLS-2$
path.insert(0, name);
}
parent = parent.parent;
}
if (path.length() > 0 && path.charAt(path.length() - 1) != '/') {
path.append("/"); //$NON-NLS-1$
}
path.append(node.name);
return path.toString();
}
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.ITreeContentProvider#hasChildren(java.lang.Object)
*/
@Override
public boolean hasChildren(final Object element) {
Assert.isNotNull(element);
boolean hasChildren = false;
if (element instanceof FSTreeNode) {
final FSTreeNode node = (FSTreeNode)element;
if (node.type != null && (node.type.endsWith("DirNode") || node.type.endsWith("RootNode"))) { //$NON-NLS-1$ //$NON-NLS-2$
if (!node.childrenQueried || node.childrenQueryRunning) {
hasChildren = true;
} else if (node.childrenQueried) {
final boolean[] result = new boolean[1];
Protocol.invokeAndWait(new Runnable() {
@Override
public void run() {
result[0] = node.getChildren().size() > 0;
}
});
hasChildren = result[0];
}
}
}
else if (element instanceof IPeerModel) {
final String[] peerId = new String[1];
if (Protocol.isDispatchThread()) {
peerId[0] = ((IPeerModel)element).getPeer().getID();
} else {
Protocol.invokeAndWait(new Runnable() {
@Override
public void run() {
peerId[0] = ((IPeerModel)element).getPeer().getID();
}
});
}
// Get the root node for this peer model object.
// If null, true is returned as it means that the file system
// model hasn't been created yet and have to treat is as children
// not queried yet.
FSTreeNode root = peerId[0] != null ? model.getRoot(peerId[0]): null;
hasChildren = root != null ? hasChildren(root) : true;
}
else {
Object adapted = adaptPeerModel(element);
if(adapted!=null){
return hasChildren(adapted);
}
}
return hasChildren;
}
/* (non-Javadoc)
* @see org.eclipse.tm.te.tcf.filesystem.internal.events.INodeStateListener#stateChanged(org.eclipse.tm.te.tcf.filesystem.model.FSTreeNode)
*/
@Override
public void stateChanged(final FSTreeNode node) {
// Make sure that this node is inside of this viewer.
Display display = PlatformUI.getWorkbench().getDisplay();
if (display.getThread() == Thread.currentThread()) {
viewer.refresh(node);
} else {
display.asyncExec(new Runnable() {
@Override
public void run() {
viewer.refresh(node);
}
});
}
}
}