blob: 14a2a4ed29e49f7effa6f2799176d83f4c71e581 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011, 2013 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 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tcf.internal.debug.ui.launch;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.core.runtime.Path;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.tcf.protocol.IChannel;
import org.eclipse.tcf.protocol.IChannel.IChannelListener;
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.DirEntry;
import org.eclipse.tcf.services.IFileSystem.FileSystemException;
import org.eclipse.tcf.services.IFileSystem.IFileHandle;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
public class FileSystemBrowserControl {
static class FileInfo {
String name;
String fullname;
boolean isDir;
FileInfo[] children;
Throwable children_error;
int index;
boolean children_pending;
FileInfo parent;
}
private Tree fileTree;
private Display fDisplay;
private IPeer fPeer;
private final FileInfo fRootInfo = new FileInfo();
private IChannel fChannel;
private IFileSystem fFileSystem;
private String fFileToSelect;
private LinkedList<String> fPathToSelect;
private FileInfo fLastSelectedFileInfo;
private final boolean fDirectoriesOnly;
public FileSystemBrowserControl(Composite parent, boolean directoriesOnly) {
fDirectoriesOnly = directoriesOnly;
fDisplay = parent.getDisplay();
parent.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
handleDispose();
}
});
createFileListArea(parent);
}
public void setInput(IPeer peer) {
if (peer == fPeer) {
return;
}
if (fPeer != null) {
Protocol.invokeAndWait(new Runnable() {
public void run() {
disconnectPeer();
}
});
}
fileTree.setItemCount(0);
fRootInfo.children = null;
fPeer = peer;
if (fPeer != null) {
Protocol.invokeAndWait(new Runnable() {
public void run() {
connectPeer();
}
});
}
}
public Tree getTree() {
return fileTree;
}
private void createFileListArea(Composite parent) {
Font font = parent.getFont();
Composite composite = new Composite(parent, SWT.NONE);
composite.setFont(font);
GridLayout layout = new GridLayout(1, false);
layout.marginWidth = 0;
layout.marginHeight = 0;
composite.setLayout(layout);
composite.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, true));
fileTree = new Tree(composite, SWT.VIRTUAL | SWT.BORDER | SWT.SINGLE);
GridData gd = new GridData(GridData.FILL_BOTH);
gd.minimumHeight = 300;
gd.minimumWidth = 350;
fileTree.setLayoutData(gd);
fileTree.setFont(font);
fileTree.addListener(SWT.SetData, new Listener() {
public void handleEvent(Event event) {
TreeItem item = (TreeItem)event.item;
FileInfo info = findFileInfo(item);
if (info == null) {
updateItems(item.getParentItem(), false);
}
else {
fillItem(item, info);
}
}
});
}
private void handleDispose() {
Protocol.invokeAndWait(new Runnable() {
public void run() {
disconnectPeer();
fFileSystem = null;
fDisplay = null;
}
});
}
protected void disconnectPeer() {
if (fChannel != null && fChannel.getState() != IChannel.STATE_CLOSED) {
fChannel.close();
}
}
protected void connectPeer() {
final IChannel channel = fPeer.openChannel();
fChannel = channel;
fFileSystem = null;
channel.addChannelListener(new IChannelListener() {
public void congestionLevel(int level) {
}
public void onChannelClosed(final Throwable error) {
if (fChannel != channel) return;
fChannel = null;
if (fDisplay != null) {
fDisplay.asyncExec(new Runnable() {
public void run() {
if (fRootInfo.children_pending) return;
fRootInfo.children = null;
fRootInfo.children_error = error;
updateItems(fRootInfo);
}
});
}
}
public void onChannelOpened() {
if (fChannel != channel) return;
fFileSystem = fChannel.getRemoteService(IFileSystem.class);
if (fFileSystem != null) {
if (fFileToSelect != null && fFileToSelect.length() > 0) {
final LinkedList<String> filePath = new LinkedList<String>();
filePath.addAll(Arrays.asList(fFileToSelect.split("[/\\\\]", -1)));
if (fFileToSelect.charAt(0) == '/') {
filePath.set(0, "/");
}
fPathToSelect = filePath;
fLastSelectedFileInfo = fRootInfo;
}
}
if (fDisplay != null) {
fDisplay.asyncExec(new Runnable() {
public void run() {
if (fRootInfo.children_pending) return;
fRootInfo.children = null;
fRootInfo.children_error = null;
updateItems(fRootInfo);
}
});
}
}
});
}
private void updateItems(TreeItem parent_item, boolean reload) {
final FileInfo parent_info = findFileInfo(parent_item);
if (parent_info == null) {
parent_item.setText("Invalid");
}
else {
if (reload && parent_info.children_error != null) {
loadChildren(parent_info);
}
fDisplay.asyncExec(new Runnable() {
public void run() {
updateItems(parent_info);
}
});
}
}
private void updateItems(final FileInfo parent) {
if (fDisplay == null) return;
assert Thread.currentThread() == fDisplay.getThread();
TreeItem[] items = null;
boolean expanded = true;
if (parent.children == null || parent.children_error != null) {
if (parent == fRootInfo) {
fileTree.setItemCount(1);
items = fileTree.getItems();
}
else {
TreeItem item = findItem(parent);
if (item == null) return;
expanded = item.getExpanded();
item.setItemCount(1);
items = item.getItems();
}
assert items.length == 1;
items[0].removeAll();
if (parent.children_pending) {
items[0].setForeground(fDisplay.getSystemColor(SWT.COLOR_LIST_FOREGROUND));
items[0].setText("Pending...");
}
else if (parent.children_error != null) {
String msg = parent.children_error.getMessage();
if (msg == null) msg = parent.children_error.getClass().getName();
else msg = msg.replace('\n', ' ');
items[0].setForeground(fDisplay.getSystemColor(SWT.COLOR_RED));
items[0].setText(msg);
items[0].setImage((Image) null);
}
else if (expanded) {
loadChildren(parent);
items[0].setForeground(fDisplay.getSystemColor(SWT.COLOR_LIST_FOREGROUND));
items[0].setText("Pending...");
}
else {
items[0].setText("");
}
}
else {
FileInfo[] arr = parent.children;
if (parent == fRootInfo) {
fileTree.setItemCount(arr.length);
items = fileTree.getItems();
}
else {
TreeItem item = findItem(parent);
if (item == null) return;
expanded = item.getExpanded();
item.setItemCount(expanded ? arr.length : 1);
items = item.getItems();
}
if (expanded) {
assert items.length == arr.length;
for (int i = 0; i < items.length; i++) fillItem(items[i], arr[i]);
expandSelect();
}
else {
items[0].setText("");
}
}
}
private void expandSelect() {
if (fPathToSelect == null) return;
if (fPathToSelect.isEmpty()) {
fPathToSelect = null;
fFileToSelect = null;
return;
}
do {
String name = fPathToSelect.getFirst();
if (name.length() == 0) {
fPathToSelect.removeFirst();
continue;
}
FileInfo info = findFileInfo(fLastSelectedFileInfo, name);
if (info == null) break;
TreeItem item = findItem(info);
if (item == null) break;
fPathToSelect.removeFirst();
if (fPathToSelect.isEmpty()) {
fileTree.setSelection(item);
fileTree.showItem(item);
} else {
item.setExpanded(true);
fileTree.showItem(item);
}
fLastSelectedFileInfo = info;
} while (!fPathToSelect.isEmpty());
}
private void loadChildren(final FileInfo parent) {
assert Thread.currentThread() == fDisplay.getThread();
if (parent.children_pending) return;
assert parent.children == null;
parent.children_pending = true;
parent.children_error = null;
Protocol.invokeLater(new Runnable() {
public void run() {
final IFileSystem fs = fFileSystem;
if (fs == null || !canHaveChildren(parent)) {
doneLoadChildren(parent, null, new FileInfo[0]);
return;
}
if (parent.fullname == null) {
fs.roots(new IFileSystem.DoneRoots() {
public void doneRoots(IToken token, FileSystemException error, DirEntry[] entries) {
if (error != null) {
doneLoadChildren(parent, error, null);
} else {
final List<FileInfo> fileInfos = new ArrayList<FileInfo>(entries.length);
for (DirEntry entry : entries) {
FileInfo info = new FileInfo();
info.parent = parent;
String name = entry.filename;
int length = name.length();
if (length > 1 && (name.endsWith("\\") || name.endsWith("/"))) {
name = name.substring(0, length - 1);
}
info.name = name;
info.fullname = entry.longname != null ? entry.longname : entry.filename;
info.isDir = entry.attrs != null ? entry.attrs.isDirectory() : false;
if (!fDirectoriesOnly || info.isDir) {
fileInfos.add(info);
}
}
doneLoadChildren(parent, null, fileInfos.toArray(new FileInfo[fileInfos.size()]));
}
}
});
return;
}
fs.opendir(parent.fullname, new IFileSystem.DoneOpen() {
final List<FileInfo> fileInfos = new ArrayList<FileInfo>();
public void doneOpen(IToken token, FileSystemException error, final IFileHandle handle) {
if (error != null) {
doneLoadChildren(parent, error, null);
return;
}
fs.readdir(handle, new IFileSystem.DoneReadDir() {
public void doneReadDir(IToken token, FileSystemException error, DirEntry[] entries, boolean eof) {
if (entries != null) {
for (DirEntry entry : entries) {
FileInfo info = new FileInfo();
info.parent = parent;
info.name = entry.filename;
info.fullname = entry.longname != null ? entry.longname : (new Path(parent.fullname).append(info.name).toString());
info.isDir = entry.attrs != null ? entry.attrs.isDirectory() : false;
if (!fDirectoriesOnly || info.isDir) {
fileInfos.add(info);
}
}
}
if (error != null || eof) {
fs.close(handle, new IFileSystem.DoneClose() {
public void doneClose(IToken token, FileSystemException error) {
// ignore error
}
});
int size = fileInfos.size();
if (size == 0 && error != null) {
doneLoadChildren(parent, error, null);
} else {
doneLoadChildren(parent, null, fileInfos.toArray(new FileInfo[size]));
}
} else {
fs.readdir(handle, this);
}
}
});
}
});
}
});
}
private void doneLoadChildren(final FileInfo parent, final Throwable error, final FileInfo[] children) {
assert Protocol.isDispatchThread();
assert error == null || children == null;
if (fDisplay == null) return;
Arrays.sort(children, new Comparator<FileInfo>() {
public int compare(FileInfo o1, FileInfo o2) {
if (o1.isDir == o2.isDir)
return o1.name.compareTo(o2.name);
if (o1.isDir) return 1;
return -1;
}});
int i = 0;
for (FileInfo fileInfo : children) {
fileInfo.index = i++;
}
fDisplay.asyncExec(new Runnable() {
public void run() {
assert parent.children_pending;
assert parent.children == null;
parent.children_pending = false;
parent.children = children;
parent.children_error = error;
updateItems(parent);
}
});
}
public FileInfo findFileInfo(TreeItem item) {
assert Thread.currentThread() == fDisplay.getThread();
if (item == null) return fRootInfo ;
TreeItem parent = item.getParentItem();
FileInfo info = findFileInfo(parent);
if (info == null) return null;
if (info.children == null) return null;
if (info.children_error != null) return null;
int i = parent == null ? fileTree.indexOf(item) : parent.indexOf(item);
if (i < 0 || i >= info.children.length) return null;
assert info.children[i].index == i;
return info.children[i];
}
private FileInfo findFileInfo(FileInfo parent, String name) {
assert Thread.currentThread() == fDisplay.getThread();
if (name == null) return fRootInfo;
if (name.equals(parent.name)) return parent;
FileInfo[] childInfos = parent.children;
if (childInfos != null) {
for (FileInfo fileInfo : childInfos) {
FileInfo found = findFileInfo(fileInfo, name);
if (found != null) {
return found;
}
}
}
return null;
}
private TreeItem findItem(FileInfo info) {
if (info == null) return null;
assert info.parent != null;
if (info.parent == fRootInfo) {
int n = fileTree.getItemCount();
if (info.index >= n) return null;
return fileTree.getItem(info.index);
}
TreeItem i = findItem(info.parent);
if (i == null) return null;
int n = i.getItemCount();
if (info.index >= n) return null;
return i.getItem(info.index);
}
private void fillItem(TreeItem item, FileInfo info) {
assert Thread.currentThread() == fDisplay.getThread();
Object data = item.getData("TCFContextInfo");
if (data != null && data != info) item.removeAll();
item.setData("TCFContextInfo", info);
String text = info.name != null ? info.name : info.fullname;
item.setText(text);
item.setForeground(fDisplay.getSystemColor(SWT.COLOR_LIST_FOREGROUND));
item.setImage(getImage(info));
if (!canHaveChildren(info)) item.setItemCount(0);
else if (info.children == null || info.children_error != null) item.setItemCount(1);
else item.setItemCount(info.children.length);
}
private boolean canHaveChildren(FileInfo info) {
return info.isDir || info == fRootInfo;
}
private Image getImage(FileInfo info) {
if (info.isDir) {
return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FOLDER);
} else {
return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FILE);
}
}
public void setInitialSelection(final String filename) {
fPathToSelect = null;
Protocol.invokeLater(new Runnable() {
public void run() {
fFileToSelect = filename;
}
});
}
public FileInfo getSelection() {
if (fileTree != null) {
TreeItem[] items = fileTree.getSelection();
if (items.length > 0) {
FileInfo info = findFileInfo(items[0]);
return info;
}
}
return null;
}
}