Bug 506109 - Add support to copy files from container to host
- add ContainerFileProxy class to model container file system
- add ContainerFileSelectionDialog for selecting files from
running Container
- add DynamicCheckboxViewer that allows dynamic reading of the
Container file system directories
- add checks to DockerConnection copyContainer method to fail
if API >= 1.24
- add DockerConnection readContainerDirectory method
- save connection info for usage in DockerConnection methods
- copy over CheckboxTreeAndListGroup from platform
- add ContainerFileSystemProvider class which traverses the
Container file system
- copy MinimizedFileSystemElement from platform
- copy PopulateRootFilesOperation from platform
- copy SelectFilesOperation from platform
- create PopulateContainerFilesPperation
- copy TarEntry, TarInputStream, and TarException classes from platform
- add CopyFromContainerCommandHandler
- add ContainerCopyFrom wizard and ContainerCopyFromPage
- add copy from container menu item to Docker Containers View
Change-Id: I5b928d6581e0ad4f8983866ad9d4bf12df7fc79b
Reviewed-on: https://git.eclipse.org/r/83396
Reviewed-by: Roland Grunberg <rgrunber@redhat.com>
Tested-by: Hudson CI
diff --git a/containers/org.eclipse.linuxtools.docker.core/META-INF/MANIFEST.MF b/containers/org.eclipse.linuxtools.docker.core/META-INF/MANIFEST.MF
index 6dfea14..c811a4c 100644
--- a/containers/org.eclipse.linuxtools.docker.core/META-INF/MANIFEST.MF
+++ b/containers/org.eclipse.linuxtools.docker.core/META-INF/MANIFEST.MF
@@ -24,6 +24,5 @@
Bundle-ActivationPolicy: lazy
Export-Package: org.eclipse.linuxtools.docker.core,
org.eclipse.linuxtools.internal.docker.core;x-friends:="org.eclipse.linuxtools.docker.ui,org.eclipse.linuxtools.docker.ui.tests"
-Import-Package: com.fasterxml.jackson.annotation;version="2.5.0",
- org.eclipse.jface.preference
+Import-Package: com.fasterxml.jackson.annotation;version="2.5.0"
Bundle-ClassPath: .
diff --git a/containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/internal/docker/core/ContainerFileProxy.java b/containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/internal/docker/core/ContainerFileProxy.java
new file mode 100644
index 0000000..5038434
--- /dev/null
+++ b/containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/internal/docker/core/ContainerFileProxy.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Red Hat.
+ * 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:
+ * Red Hat - Initial Contribution
+ *******************************************************************************/
+package org.eclipse.linuxtools.internal.docker.core;
+
+public class ContainerFileProxy {
+
+ private final String path;
+ private final String name;
+ private final String link;
+ private final boolean isFolder;
+ private final boolean isLink;
+
+ public ContainerFileProxy(String directory, String name,
+ boolean isFolder) {
+ this.path = directory + (directory.equals("/") ? "" : "/") + name; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ this.name = name;
+ this.isFolder = isFolder;
+ this.isLink = false;
+ this.link = this.path;
+ }
+
+ public ContainerFileProxy(String directory, String name, boolean isFolder,
+ boolean isLink, String link) {
+ this.path = directory + (directory.equals("/") ? "" : "/") + name; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ this.name = name;
+ this.isFolder = isFolder;
+ this.isLink = isLink;
+ this.link = (link == null ? this.path : link);
+ }
+
+ public String getFullPath() {
+ return path;
+ }
+
+ public String getLabel() {
+ return name + (isFolder() ? "/" : "");
+ }
+
+ public boolean isFolder() {
+ return isFolder;
+ }
+
+ public boolean isLink() {
+ return isLink;
+ }
+
+ public String getLink() {
+ return link;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return getFullPath();
+ }
+
+}
diff --git a/containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/internal/docker/core/DockerConnection.java b/containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/internal/docker/core/DockerConnection.java
index 4eacc3a..f650bdf 100644
--- a/containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/internal/docker/core/DockerConnection.java
+++ b/containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/internal/docker/core/DockerConnection.java
@@ -37,6 +37,7 @@
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
+import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
@@ -165,6 +166,7 @@
private String name;
private IDockerConnectionSettings connectionSettings;
+ private IDockerConnectionInfo connectionInfo;
private final String username;
private final Object imageLock = new Object();
private final Object containerLock = new Object();
@@ -281,6 +283,12 @@
public void setClient(final DockerClient client) {
this.client = client;
+ try {
+ this.connectionInfo = getInfo();
+ } catch (Exception e) {
+ // ignore for now as this seems to occur too often and we always
+ // check the value of connectioninfo before using
+ }
}
/**
* Change the default {@link DockerClientFactory}
@@ -1831,7 +1839,28 @@
throws DockerException, InterruptedException {
InputStream stream;
try {
- stream = client.copyContainer(id, path);
+ if (this.connectionInfo != null) {
+ String apiversion = connectionInfo.getApiVersion();
+ if (apiversion != null) {
+ String[] tokens = apiversion.split("\\."); //$NON-NLS-1$
+ if (tokens.length > 1) {
+ try {
+ int major = Integer.valueOf(tokens[0]);
+ int minor = Integer.valueOf(tokens[1]);
+ if (major > 1 || minor >= 24) {
+ throw new DockerException(
+ DockerMessages.getFormattedString(
+ "DockerClientVersionTooLow.error", //$NON-NLS-1$
+ "copyContainer", "1.24")); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ } catch (NumberFormatException e) {
+ // ignore for now and let things occur
+ }
+ }
+ }
+ }
+ DockerClient copy = getClientCopy();
+ stream = copy.copyContainer(id, path);
} catch (com.spotify.docker.client.DockerException e) {
throw new DockerException(e.getMessage(), e.getCause());
}
@@ -1998,6 +2027,68 @@
}
}
+ @SuppressWarnings("unused")
+ public List<ContainerFileProxy> readContainerDirectory(final String id,
+ final String path) throws DockerException {
+ List<ContainerFileProxy> childList = new ArrayList<>();
+ try {
+ DockerClient copyClient = getClientCopy();
+ final String execId = copyClient.execCreate(id,
+ new String[] { "/bin/sh", "-c", "ls -l -F -L -Q " + path }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ ExecCreateParam.attachStdout(),
+ ExecCreateParam.attachStderr());
+ final LogStream pty_stream = copyClient.execStart(execId);
+ try {
+ while (pty_stream.hasNext()) {
+ ByteBuffer b = pty_stream.next().content();
+ byte[] buffer = new byte[b.remaining()];
+ b.get(buffer);
+ String s = new String(buffer);
+ String[] lines = s.split("\\r?\\n"); //$NON-NLS-1$
+ for (String line : lines) {
+ if (line.trim().startsWith("total")) //$NON-NLS-1$
+ continue; // ignore the total line
+ String[] token = line.split("\\s+"); //$NON-NLS-1$
+ boolean isDirectory = token[0].startsWith("d"); //$NON-NLS-1$
+ boolean isLink = token[0].startsWith("l"); //$NON-NLS-1$
+ if (token.length > 8) {
+ // last token depends on whether we have a link or not
+ String link = null;
+ if (isLink) {
+ String linkname = token[token.length - 1];
+ if (linkname.endsWith("/")) { //$NON-NLS-1$
+ linkname = linkname.substring(0, linkname.length() - 1);
+ isDirectory = true;
+ }
+ IPath linkPath = new Path(path);
+ linkPath = linkPath.append(linkname);
+ link = linkPath.toString();
+ String name = token[token.length - 3];
+ childList.add(new ContainerFileProxy(path, name,
+ isDirectory, isLink, link));
+ } else {
+ String name = token[token.length - 1];
+ // remove quotes and any indicator char
+ name = name.substring(1, name.length()
+ - (name.endsWith("\"") ? 1 : 2));
+ childList.add(new ContainerFileProxy(path, name,
+ isDirectory));
+ }
+ }
+ }
+ }
+ } finally {
+ if (pty_stream != null)
+ pty_stream.close();
+ if (copyClient != null)
+ copyClient.close();
+ }
+ } catch (Exception e) {
+ // e.printStackTrace();
+ }
+ return childList;
+ }
+
public void execShell(final String id) throws DockerException {
try {
final String execId = client.execCreate(id,
diff --git a/containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/internal/docker/core/DockerMessages.properties b/containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/internal/docker/core/DockerMessages.properties
index 56ed190..e408840 100644
--- a/containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/internal/docker/core/DockerMessages.properties
+++ b/containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/internal/docker/core/DockerMessages.properties
@@ -27,3 +27,5 @@
ImageBuildStep.msg=Step
ImageBuildingJobName.msg=Building Docker Image - {0}
ImageBuilding.msg=Building image
+
+DockerClientVersionTooLow.error=The version of docker client cannot support {0} using a daemon with API version: {1} and higher.
diff --git a/containers/org.eclipse.linuxtools.docker.ui/.settings/.api_filters b/containers/org.eclipse.linuxtools.docker.ui/.settings/.api_filters
new file mode 100644
index 0000000..639132e
--- /dev/null
+++ b/containers/org.eclipse.linuxtools.docker.ui/.settings/.api_filters
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<component id="org.eclipse.linuxtools.docker.ui" version="2">
+ <resource path="src/org/eclipse/linuxtools/internal/docker/ui/DynamicCheckboxTreeViewer.java" type="org.eclipse.linuxtools.internal.docker.ui.DynamicCheckboxTreeViewer">
+ <filter id="571473929">
+ <message_arguments>
+ <message_argument value="CheckboxTreeViewer"/>
+ <message_argument value="DynamicCheckboxTreeViewer"/>
+ </message_arguments>
+ </filter>
+ </resource>
+</component>
diff --git a/containers/org.eclipse.linuxtools.docker.ui/plugin.properties b/containers/org.eclipse.linuxtools.docker.ui/plugin.properties
index 21a9461..a237362 100644
--- a/containers/org.eclipse.linuxtools.docker.ui/plugin.properties
+++ b/containers/org.eclipse.linuxtools.docker.ui/plugin.properties
@@ -72,6 +72,9 @@
command.commitcontainer.name=Commit
command.commitcontainer.description=Commit the selected container into a new image
+command.copyfromcontainer.name=Copy from Container
+command.copyfromcontainer.description=Copy files from running Container to a local directory
+
command.displaycontainerlog.name=Display Log
command.displaycontainerlog.description=Display the log for the selected container in the Console
diff --git a/containers/org.eclipse.linuxtools.docker.ui/plugin.xml b/containers/org.eclipse.linuxtools.docker.ui/plugin.xml
index a0a066b..584bb34 100644
--- a/containers/org.eclipse.linuxtools.docker.ui/plugin.xml
+++ b/containers/org.eclipse.linuxtools.docker.ui/plugin.xml
@@ -265,6 +265,11 @@
name="%command.commitcontainer.name">
</command>
<command
+ description="%command.copyfromcontainer.description"
+ id="org.eclipse.linuxtools.docker.ui.commands.copyfromcontainer"
+ name="%command.copyfromcontainer.name">
+ </command>
+ <command
description="%command.displaycontainerlog.description"
id="org.eclipse.linuxtools.docker.ui.commands.displayContainerLog"
name="%command.displaycontainerlog.name">
@@ -657,6 +662,38 @@
</with>
</enabledWhen>
</handler>
+ <handler
+ class="org.eclipse.linuxtools.internal.docker.ui.commands.CopyFromContainerCommandHandler"
+ commandId="org.eclipse.linuxtools.docker.ui.commands.copyfromcontainer">
+ <enabledWhen>
+ <with
+ variable="selection">
+ <count
+ value="1">
+ </count>
+ <iterate
+ ifEmpty="false">
+ <and>
+ <instanceof
+ value="org.eclipse.linuxtools.docker.core.IDockerContainer">
+ </instanceof>
+ <or>
+ <test
+ forcePluginActivation="true"
+ property="org.eclipse.linuxtools.docker.propertytesters.container.isStopped"
+ value="false">
+ </test>
+ <test
+ forcePluginActivation="true"
+ property="org.eclipse.linuxtools.docker.propertytesters.container.isUnknown"
+ value="true">
+ </test>
+ </or>
+ </and>
+ </iterate>
+ </with>
+ </enabledWhen>
+ </handler>
<handler
commandId="org.eclipse.linuxtools.docker.ui.commands.displayContainerLog"
class="org.eclipse.linuxtools.internal.docker.ui.commands.DisplayContainerLogCommandHandler">
@@ -1646,6 +1683,15 @@
style="push">
</command>
</menuContribution>
+ <!-- containers view context menu: copy from containers -->
+ <menuContribution
+ locationURI="popup:org.eclipse.linuxtools.docker.ui.dockerContainersView">
+ <command
+ commandId="org.eclipse.linuxtools.docker.ui.commands.copyfromcontainer"
+ id="org.eclipse.linuxtools.docker.ui.commands.copyfromcontainer"
+ style="push">
+ </command>
+ </menuContribution>
<!-- containers view toolbar: refresh view -->
<menuContribution
diff --git a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/CheckboxTreeAndListGroup.java b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/CheckboxTreeAndListGroup.java
new file mode 100644
index 0000000..2c1991c
--- /dev/null
+++ b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/CheckboxTreeAndListGroup.java
@@ -0,0 +1,864 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 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.linuxtools.internal.docker.ui;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.eclipse.core.commands.common.EventManager;
+import org.eclipse.core.runtime.SafeRunner;
+import org.eclipse.jface.util.SafeRunnable;
+import org.eclipse.jface.viewers.CheckStateChangedEvent;
+import org.eclipse.jface.viewers.CheckboxTableViewer;
+import org.eclipse.jface.viewers.CheckboxTreeViewer;
+import org.eclipse.jface.viewers.ICheckStateListener;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.ITreeViewerListener;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.TreeExpansionEvent;
+import org.eclipse.jface.viewers.ViewerComparator;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.BusyIndicator;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.Tree;
+
+/**
+ * Workbench-level composite that combines a CheckboxTreeViewer and
+ * CheckboxListViewer. All viewer selection-driven interactions are handled
+ * within this object
+ */
+@SuppressWarnings("rawtypes")
+public class CheckboxTreeAndListGroup extends EventManager implements
+ ICheckStateListener, ISelectionChangedListener, ITreeViewerListener {
+ private Object root;
+
+ private Object currentTreeSelection;
+
+ private List expandedTreeNodes = new ArrayList();
+
+ private Map checkedStateStore = new HashMap(9);
+
+ private List whiteCheckedTreeItems = new ArrayList();
+
+ private ITreeContentProvider treeContentProvider;
+
+ private ITreeContentProvider dynamicTreeContentProvider;
+
+ private IStructuredContentProvider listContentProvider;
+
+ private ILabelProvider treeLabelProvider;
+
+ private ILabelProvider listLabelProvider;
+
+ // widgets
+ private CheckboxTreeViewer treeViewer;
+
+ private CheckboxTableViewer listViewer;
+
+ /**
+ * Create an instance of this class. Use this constructor if you wish to
+ * specify the width and/or height of the combined widget (to only hardcode
+ * one of the sizing dimensions, specify the other dimension's value as -1)
+ *
+ * @param parent
+ * @param rootObject
+ * @param treeContentProvider
+ * @param treeLabelProvider
+ * @param listContentProvider
+ * @param listLabelProvider
+ * @param style
+ * @param width
+ * @param height
+ */
+ public CheckboxTreeAndListGroup(Composite parent, Object rootObject,
+ ITreeContentProvider treeContentProvider,
+ ITreeContentProvider dynamicTreeContentProvider,
+ ILabelProvider treeLabelProvider,
+ IStructuredContentProvider listContentProvider,
+ ILabelProvider listLabelProvider, int style, int width,
+ int height) {
+
+ root = rootObject;
+ this.treeContentProvider = treeContentProvider;
+ this.dynamicTreeContentProvider = dynamicTreeContentProvider;
+ this.listContentProvider = listContentProvider;
+ this.treeLabelProvider = treeLabelProvider;
+ this.listLabelProvider = listLabelProvider;
+ createContents(parent, width, height, style);
+ }
+
+ /**
+ * This method must be called just before this window becomes visible.
+ */
+ public void aboutToOpen() {
+ determineWhiteCheckedDescendents(root);
+ checkNewTreeElements(treeContentProvider.getElements(root));
+ currentTreeSelection = null;
+
+ // select the first element in the list
+ Object[] elements = treeContentProvider.getElements(root);
+ Object primary = elements.length > 0 ? elements[0] : null;
+ if (primary != null) {
+ treeViewer.setSelection(new StructuredSelection(primary));
+ }
+ treeViewer.getControl().setFocus();
+ }
+
+ /**
+ * Add the passed listener to self's collection of clients that listen for
+ * changes to element checked states
+ *
+ * @param listener
+ * ICheckStateListener
+ */
+ public void addCheckStateListener(ICheckStateListener listener) {
+ addListenerObject(listener);
+ }
+
+ /**
+ * Add the receiver and all of it's ancestors to the checkedStateStore if
+ * they are not already there.
+ */
+ @SuppressWarnings("unchecked")
+ private void addToHierarchyToCheckedStore(Object treeElement) {
+
+ // if this tree element is already gray then its ancestors all are as
+ // well
+ if (!checkedStateStore.containsKey(treeElement)) {
+ checkedStateStore.put(treeElement, new ArrayList());
+ }
+
+ Object parent = treeContentProvider.getParent(treeElement);
+ if (parent != null) {
+ addToHierarchyToCheckedStore(parent);
+ }
+ }
+
+ /**
+ * Return a boolean indicating whether all children of the passed tree
+ * element are currently white-checked
+ *
+ * @return boolean
+ * @param treeElement
+ * java.lang.Object
+ */
+ protected boolean areAllChildrenWhiteChecked(Object treeElement) {
+ Object[] children = treeContentProvider.getChildren(treeElement);
+ for (int i = 0; i < children.length; ++i) {
+ if (!whiteCheckedTreeItems.contains(children[i])) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Return a boolean indicating whether all list elements associated with the
+ * passed tree element are currently checked
+ *
+ * @return boolean
+ * @param treeElement
+ * java.lang.Object
+ */
+ protected boolean areAllElementsChecked(Object treeElement) {
+ List checkedElements = (List) checkedStateStore.get(treeElement);
+ if (checkedElements == null) {
+ return false;
+ }
+
+ return getListItemsSize(treeElement) == checkedElements.size();
+ }
+
+ /**
+ * Iterate through the passed elements which are being realized for the
+ * first time and check each one in the tree viewer as appropriate
+ */
+ protected void checkNewTreeElements(Object[] elements) {
+ for (int i = 0; i < elements.length; ++i) {
+ Object currentElement = elements[i];
+ boolean checked = checkedStateStore.containsKey(currentElement);
+ treeViewer.setChecked(currentElement, checked);
+ treeViewer.setGrayed(currentElement,
+ checked && !whiteCheckedTreeItems.contains(currentElement));
+ }
+ }
+
+ /**
+ * An item was checked in one of self's two views. Determine which view this
+ * occurred in and delegate appropriately
+ *
+ * @param event
+ * CheckStateChangedEvent
+ */
+ @Override
+ public void checkStateChanged(final CheckStateChangedEvent event) {
+
+ // Potentially long operation - show a busy cursor
+ BusyIndicator.showWhile(treeViewer.getControl().getDisplay(), () -> {
+ if (event.getCheckable().equals(treeViewer)) {
+ treeItemChecked(event.getElement(), event.getChecked());
+ } else {
+ listItemChecked(event.getElement(), event.getChecked(), true);
+ }
+
+ notifyCheckStateChangeListeners(event);
+ });
+ }
+
+ /**
+ * Lay out and initialize self's visual components.
+ *
+ * @param parent
+ * org.eclipse.swt.widgets.Composite
+ * @param width
+ * int
+ * @param height
+ * int
+ */
+ protected void createContents(Composite parent, int width, int height,
+ int style) {
+ // group pane
+ Composite composite = new Composite(parent, style);
+ GridLayout layout = new GridLayout();
+ layout.numColumns = 2;
+ layout.makeColumnsEqualWidth = true;
+ layout.marginHeight = 0;
+ layout.marginWidth = 0;
+ composite.setLayout(layout);
+ composite.setLayoutData(new GridData(GridData.FILL_BOTH));
+ composite.setFont(parent.getFont());
+
+ createTreeViewer(composite, width / 2, height);
+ createListViewer(composite, width / 2, height);
+
+ initialize();
+ }
+
+ /**
+ * Create this group's list viewer.
+ */
+ protected void createListViewer(Composite parent, int width, int height) {
+ listViewer = CheckboxTableViewer.newCheckList(parent, SWT.BORDER);
+ GridData data = new GridData(GridData.FILL_BOTH);
+ data.widthHint = width;
+ data.heightHint = height;
+ listViewer.getTable().setLayoutData(data);
+ listViewer.getTable().setFont(parent.getFont());
+ listViewer.setContentProvider(listContentProvider);
+ listViewer.setLabelProvider(listLabelProvider);
+ listViewer.addCheckStateListener(this);
+ }
+
+ /**
+ * Create this group's tree viewer.
+ */
+ protected void createTreeViewer(Composite parent, int width, int height) {
+ Tree tree = new Tree(parent, SWT.CHECK | SWT.BORDER);
+ GridData data = new GridData(GridData.FILL_BOTH);
+ data.widthHint = width;
+ data.heightHint = height;
+ tree.setLayoutData(data);
+ tree.setFont(parent.getFont());
+
+ treeViewer = new DynamicCheckboxTreeViewer(tree,
+ dynamicTreeContentProvider);
+ treeViewer.setContentProvider(treeContentProvider);
+ treeViewer.setLabelProvider(treeLabelProvider);
+ treeViewer.addTreeListener(this);
+ treeViewer.addCheckStateListener(this);
+ treeViewer.addSelectionChangedListener(this);
+ }
+
+ /**
+ * Returns a boolean indicating whether the passed tree element should be at
+ * LEAST gray-checked. Note that this method does not consider whether it
+ * should be white-checked, so a specified tree item which should be
+ * white-checked will result in a <code>true</code> answer from this method.
+ * To determine whether a tree item should be white-checked use method
+ * #determineShouldBeWhiteChecked(Object).
+ *
+ * @param treeElement
+ * java.lang.Object
+ * @return boolean
+ * @see #determineShouldBeWhiteChecked(java.lang.Object)
+ */
+ protected boolean determineShouldBeAtLeastGrayChecked(Object treeElement) {
+ // if any list items associated with treeElement are checked then it
+ // retains its gray-checked status regardless of its children
+ List checked = (List) checkedStateStore.get(treeElement);
+ if (checked != null && (!checked.isEmpty())) {
+ return true;
+ }
+
+ // if any children of treeElement are still gray-checked then
+ // treeElement
+ // must remain gray-checked as well
+ Object[] children = treeContentProvider.getChildren(treeElement);
+ for (int i = 0; i < children.length; ++i) {
+ if (checkedStateStore.containsKey(children[i])) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns a boolean indicating whether the passed tree item should be
+ * white-checked.
+ *
+ * @return boolean
+ * @param treeElement
+ * java.lang.Object
+ */
+ protected boolean determineShouldBeWhiteChecked(Object treeElement) {
+ return areAllChildrenWhiteChecked(treeElement)
+ && areAllElementsChecked(treeElement);
+ }
+
+ /**
+ * Recursively add appropriate tree elements to the collection of known
+ * white-checked tree elements.
+ *
+ * @param treeElement
+ * java.lang.Object
+ */
+ protected void determineWhiteCheckedDescendents(Object treeElement) {
+ // always go through all children first since their white-checked
+ // statuses will be needed to determine the white-checked status for
+ // this tree element
+ Object[] children = treeContentProvider.getElements(treeElement);
+ for (int i = 0; i < children.length; ++i) {
+ determineWhiteCheckedDescendents(children[i]);
+ }
+
+ // now determine the white-checked status for this tree element
+ if (determineShouldBeWhiteChecked(treeElement)) {
+ setWhiteChecked(treeElement, true);
+ }
+ }
+
+ /**
+ * Cause the tree viewer to expand all its items
+ */
+ public void expandAll() {
+ treeViewer.expandAll();
+ }
+
+ /**
+ * Answer a flat collection of all of the checked elements in the list
+ * portion of self
+ *
+ * @return java.util.Vector
+ */
+ @SuppressWarnings("unchecked")
+ public Iterator getAllCheckedListItems() {
+ List result = new ArrayList();
+ Iterator listCollectionsEnum = checkedStateStore.values().iterator();
+
+ while (listCollectionsEnum.hasNext()) {
+ Iterator currentCollection = ((List) listCollectionsEnum.next())
+ .iterator();
+ while (currentCollection.hasNext()) {
+ result.add(currentCollection.next());
+ }
+ }
+
+ return result.iterator();
+ }
+
+ /**
+ * Answer a collection of all of the checked elements in the tree portion of
+ * self
+ *
+ * @return java.util.Vector
+ */
+ public Set getAllCheckedTreeItems() {
+ return checkedStateStore.keySet();
+ }
+
+ /**
+ * Answer the number of elements that have been checked by the user.
+ *
+ * @return int
+ */
+ public int getCheckedElementCount() {
+ return checkedStateStore.size();
+ }
+
+ /**
+ * Return a count of the number of list items associated with a given tree
+ * item.
+ *
+ * @return int
+ * @param treeElement
+ * java.lang.Object
+ */
+ protected int getListItemsSize(Object treeElement) {
+ Object[] elements = listContentProvider.getElements(treeElement);
+ return elements.length;
+ }
+
+ /**
+ * Get the table the list viewer uses.
+ *
+ * @return org.eclipse.swt.widgets.Table
+ */
+ public Table getListTable() {
+ return this.listViewer.getTable();
+ }
+
+ /**
+ * Logically gray-check all ancestors of treeItem by ensuring that they
+ * appear in the checked table
+ */
+ @SuppressWarnings("unchecked")
+ protected void grayCheckHierarchy(Object treeElement) {
+
+ // if this tree element is already gray then its ancestors all are as
+ // well
+ if (checkedStateStore.containsKey(treeElement)) {
+ return; // no need to proceed upwards from here
+ }
+
+ checkedStateStore.put(treeElement, new ArrayList());
+ if (determineShouldBeWhiteChecked(treeElement)) {
+ setWhiteChecked(treeElement, true);
+ }
+ Object parent = treeContentProvider.getParent(treeElement);
+ if (parent != null) {
+ grayCheckHierarchy(parent);
+ }
+ }
+
+ /**
+ * Set the initial checked state of the passed list element to true.
+ *
+ * @param element
+ * the element in the list to select
+ */
+ public void initialCheckListItem(Object element) {
+ Object parent = treeContentProvider.getParent(element);
+ currentTreeSelection = parent;
+ // As this is not done from the UI then set the box for updating from
+ // the selection to false
+ listItemChecked(element, true, false);
+ updateHierarchy(parent);
+ }
+
+ /**
+ * Set the initial checked state of the passed element to true, as well as
+ * to all of its children and associated list elements
+ *
+ * @param element
+ * the element in the tree to select
+ */
+ public void initialCheckTreeItem(Object element) {
+ treeItemChecked(element, true);
+ }
+
+ /**
+ * Initialize this group's viewers after they have been laid out.
+ */
+ protected void initialize() {
+ treeViewer.setInput(root);
+ }
+
+ /**
+ * Callback that's invoked when the checked status of an item in the list is
+ * changed by the user. Do not try and update the hierarchy if we are
+ * building the initial list.
+ */
+ @SuppressWarnings("unchecked")
+ protected void listItemChecked(Object listElement, boolean state,
+ boolean updatingFromSelection) {
+ List checkedListItems = (List) checkedStateStore
+ .get(currentTreeSelection);
+
+ if (state) {
+ if (checkedListItems == null) {
+ // since the associated tree item has gone from 0 -> 1 checked
+ // list items, tree checking may need to be updated
+ grayCheckHierarchy(currentTreeSelection);
+ checkedListItems = (List) checkedStateStore
+ .get(currentTreeSelection);
+ }
+ checkedListItems.add(listElement);
+ } else {
+ checkedListItems.remove(listElement);
+ if (checkedListItems.isEmpty()) {
+ // since the associated tree item has gone from 1 -> 0 checked
+ // list items, tree checking may need to be updated
+ ungrayCheckHierarchy(currentTreeSelection);
+ }
+ }
+
+ if (updatingFromSelection) {
+ updateHierarchy(currentTreeSelection);
+ }
+ }
+
+ /**
+ * Notify all checked state listeners that the passed element has had its
+ * checked state changed to the passed state
+ */
+ protected void notifyCheckStateChangeListeners(
+ final CheckStateChangedEvent event) {
+ Object[] array = getListeners();
+ for (int i = 0; i < array.length; i++) {
+ final ICheckStateListener l = (ICheckStateListener) array[i];
+ SafeRunner.run(new SafeRunnable() {
+ @Override
+ public void run() {
+ l.checkStateChanged(event);
+ }
+ });
+ }
+ }
+
+ /**
+ * Set the contents of the list viewer based upon the specified selected
+ * tree element. This also includes checking the appropriate list items.
+ *
+ * @param treeElement
+ * java.lang.Object
+ */
+ protected void populateListViewer(final Object treeElement) {
+ listViewer.setInput(treeElement);
+ List listItemsToCheck = (List) checkedStateStore.get(treeElement);
+
+ if (listItemsToCheck != null) {
+ Iterator listItemsEnum = listItemsToCheck.iterator();
+ while (listItemsEnum.hasNext()) {
+ listViewer.setChecked(listItemsEnum.next(), true);
+ }
+ }
+ }
+
+ /**
+ * Remove the passed listener from self's collection of clients that listen
+ * for changes to element checked states
+ *
+ * @param listener
+ * ICheckStateListener
+ */
+ public void removeCheckStateListener(ICheckStateListener listener) {
+ removeListenerObject(listener);
+ }
+
+ /**
+ * Handle the selection of an item in the tree viewer
+ *
+ * @param event
+ * SelectionChangedEvent
+ */
+ @Override
+ public void selectionChanged(SelectionChangedEvent event) {
+ IStructuredSelection selection = (IStructuredSelection) event
+ .getSelection();
+ Object selectedElement = selection.getFirstElement();
+ if (selectedElement == null) {
+ currentTreeSelection = null;
+ listViewer.setInput(currentTreeSelection);
+ return;
+ }
+
+ // ie.- if not an item deselection
+ if (selectedElement != currentTreeSelection) {
+ populateListViewer(selectedElement);
+ }
+
+ currentTreeSelection = selectedElement;
+ }
+
+ /**
+ * Select or deselect all of the elements in the tree depending on the value
+ * of the selection boolean. Be sure to update the displayed files as well.
+ *
+ * @param selection
+ * boolean indicating whether or not to select all elements
+ */
+ public void setAllSelections(final boolean selection) {
+
+ // Potentially long operation - show a busy cursor
+ BusyIndicator.showWhile(treeViewer.getControl().getDisplay(), () -> {
+ setTreeChecked(root, selection);
+ listViewer.setAllChecked(selection);
+ });
+ }
+
+ /**
+ * Set the list viewer's providers to those passed
+ *
+ * @param contentProvider
+ * ITreeContentProvider
+ * @param labelProvider
+ * ILabelProvider
+ */
+ public void setListProviders(IStructuredContentProvider contentProvider,
+ ILabelProvider labelProvider) {
+ listViewer.setContentProvider(contentProvider);
+ listViewer.setLabelProvider(labelProvider);
+ }
+
+ /**
+ * Set the comparator that is to be applied to self's list viewer
+ *
+ * @param comparator
+ * the comparator for the list viewer
+ */
+ public void setListComparator(ViewerComparator comparator) {
+ listViewer.setComparator(comparator);
+ }
+
+ /**
+ * Set the root of the widget to be new Root. Regenerate all of the tables
+ * and lists from this value.
+ *
+ * @param newRoot
+ */
+ public void setRoot(Object newRoot) {
+ this.root = newRoot;
+ initialize();
+ }
+
+ /**
+ * Set the checked state of the passed tree element appropriately, and do so
+ * recursively to all of its child tree elements as well
+ */
+ @SuppressWarnings("unchecked")
+ protected void setTreeChecked(Object treeElement, boolean state) {
+
+ if (treeElement.equals(currentTreeSelection)) {
+ listViewer.setAllChecked(state);
+ }
+
+ if (state) {
+ Object[] listItems = listContentProvider.getElements(treeElement);
+ List listItemsChecked = new ArrayList();
+ for (int i = 0; i < listItems.length; ++i) {
+ listItemsChecked.add(listItems[i]);
+ }
+
+ checkedStateStore.put(treeElement, listItemsChecked);
+ } else {
+ checkedStateStore.remove(treeElement);
+ }
+
+ setWhiteChecked(treeElement, state);
+ treeViewer.setChecked(treeElement, state);
+ treeViewer.setGrayed(treeElement, false);
+
+ // now logically check/uncheck all children as well
+ Object[] children = treeContentProvider.getChildren(treeElement);
+ for (int i = 0; i < children.length; ++i) {
+ setTreeChecked(children[i], state);
+ }
+ }
+
+ /**
+ * Set the tree viewer's providers to those passed
+ *
+ * @param contentProvider
+ * ITreeContentProvider
+ * @param labelProvider
+ * ILabelProvider
+ */
+ public void setTreeProviders(ITreeContentProvider contentProvider,
+ ILabelProvider labelProvider) {
+ treeViewer.setContentProvider(contentProvider);
+ treeViewer.setLabelProvider(labelProvider);
+ }
+
+ /**
+ * Set the comparator that is to be applied to self's tree viewer
+ *
+ * @param comparator
+ * the comparator for the tree
+ */
+ public void setTreeComparator(ViewerComparator comparator) {
+ treeViewer.setComparator(comparator);
+ }
+
+ /**
+ * Adjust the collection of references to white-checked tree elements
+ * appropriately.
+ *
+ * @param treeElement
+ * java.lang.Object
+ * @param isWhiteChecked
+ * boolean
+ */
+ @SuppressWarnings("unchecked")
+ protected void setWhiteChecked(Object treeElement, boolean isWhiteChecked) {
+ if (isWhiteChecked) {
+ if (!whiteCheckedTreeItems.contains(treeElement)) {
+ whiteCheckedTreeItems.add(treeElement);
+ }
+ } else {
+ whiteCheckedTreeItems.remove(treeElement);
+ }
+ }
+
+ /**
+ * Handle the collapsing of an element in a tree viewer
+ */
+ @Override
+ public void treeCollapsed(TreeExpansionEvent event) {
+ // We don't need to do anything with this
+ }
+
+ /**
+ * Handle the expansionsion of an element in a tree viewer
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public void treeExpanded(TreeExpansionEvent event) {
+
+ Object item = event.getElement();
+
+ // First see if the children need to be given their checked state at
+ // all. If they've
+ // already been realized then this won't be necessary
+ if (!expandedTreeNodes.contains(item)) {
+ expandedTreeNodes.add(item);
+ checkNewTreeElements(dynamicTreeContentProvider.getChildren(item));
+ Object[] children = treeContentProvider.getElements(item);
+ for (int i = 0; i < children.length; ++i) {
+ dynamicTreeContentProvider.getElements(children[i]);
+ }
+ }
+ }
+
+ /**
+ * Callback that's invoked when the checked status of an item in the tree is
+ * changed by the user.
+ */
+ protected void treeItemChecked(Object treeElement, boolean state) {
+
+ // recursively adjust all child tree elements appropriately
+ setTreeChecked(treeElement, state);
+
+ Object parent = treeContentProvider.getParent(treeElement);
+ if (parent == null) {
+ return;
+ }
+
+ // now update upwards in the tree hierarchy
+ if (state) {
+ grayCheckHierarchy(parent);
+ } else {
+ ungrayCheckHierarchy(parent);
+ }
+
+ updateHierarchy(treeElement);
+ }
+
+ /**
+ * Logically un-gray-check all ancestors of treeItem iff appropriate.
+ */
+ protected void ungrayCheckHierarchy(Object treeElement) {
+ if (!determineShouldBeAtLeastGrayChecked(treeElement)) {
+ checkedStateStore.remove(treeElement);
+ }
+
+ Object parent = treeContentProvider.getParent(treeElement);
+ if (parent != null) {
+ ungrayCheckHierarchy(parent);
+ }
+ }
+
+ /**
+ * Set the checked state of self and all ancestors appropriately
+ */
+ protected void updateHierarchy(Object treeElement) {
+
+ boolean whiteChecked = determineShouldBeWhiteChecked(treeElement);
+ boolean shouldBeAtLeastGray = determineShouldBeAtLeastGrayChecked(
+ treeElement);
+
+ treeViewer.setChecked(treeElement, shouldBeAtLeastGray);
+ setWhiteChecked(treeElement, whiteChecked);
+ if (!whiteChecked) {
+ treeViewer.setGrayed(treeElement, shouldBeAtLeastGray);
+ }
+
+ // proceed up the tree element hierarchy
+ Object parent = treeContentProvider.getParent(treeElement);
+ if (parent != null) {
+ updateHierarchy(parent);
+ }
+ }
+
+ /**
+ * Update the selections of the tree elements in items to reflect the new
+ * selections provided.
+ *
+ * @param items
+ * Map with keys of Object (the tree element) and values of List
+ * (the selected list elements).
+ */
+ @SuppressWarnings("unchecked")
+ public void updateSelections(final Map items) {
+
+ // Potentially long operation - show a busy cursor
+ BusyIndicator.showWhile(treeViewer.getControl().getDisplay(), () -> {
+ // Update the store before the hierarchy to prevent updating parents
+ // before all of the children are done
+ for (Entry<?, List> entry : ((Map<Object, List>) items)
+ .entrySet()) {
+ Object key1 = entry.getKey();
+ // Replace the items in the checked state store with those from
+ // the supplied items
+ List selections = entry.getValue();
+ if (selections.size() == 0) {
+ // If it is empty remove it from the list
+ checkedStateStore.remove(key1);
+ } else {
+ checkedStateStore.put(key1, selections);
+ // proceed up the tree element hierarchy
+ Object parent = treeContentProvider.getParent(key1);
+ if (parent != null) {
+ addToHierarchyToCheckedStore(parent);
+ }
+ }
+ }
+
+ // Now update hierarchies
+ for (Entry<Object, List> entry : ((Map<Object, List>) items)
+ .entrySet()) {
+ Object key2 = entry.getKey();
+ updateHierarchy(key2);
+ if (currentTreeSelection != null
+ && currentTreeSelection.equals(key2)) {
+ listViewer.setAllChecked(false);
+ listViewer.setCheckedElements(entry.getValue().toArray());
+ }
+ }
+ });
+
+ }
+}
+
diff --git a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/ContainerFileSelectionDialog.java b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/ContainerFileSelectionDialog.java
new file mode 100644
index 0000000..6d2a014
--- /dev/null
+++ b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/ContainerFileSelectionDialog.java
@@ -0,0 +1,307 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Red Hat 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:
+ * Red Hat - Initial Contribution
+ *******************************************************************************/
+package org.eclipse.linuxtools.internal.docker.ui;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import org.eclipse.jface.viewers.ICheckStateListener;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.dialogs.FileSystemElement;
+import org.eclipse.ui.dialogs.SelectionDialog;
+import org.eclipse.ui.model.WorkbenchContentProvider;
+import org.eclipse.ui.model.WorkbenchLabelProvider;
+import org.eclipse.ui.model.WorkbenchViewerComparator;
+import org.eclipse.ui.wizards.datatransfer.IImportStructureProvider;
+
+/**
+ * A standard file selection dialog which solicits a list of files from the user.
+ * The <code>getResult</code> method returns the selected files.
+ * <p>
+ * This class may be instantiated; it is not intended to be subclassed.
+ * </p>
+ * <p>
+ * Example:
+ * <pre>
+ * FileSelectionDialog dialog =
+ * new FileSelectionDialog(getShell(), rootElement, msg);
+ * dialog.setInitialSelections(selectedResources);
+ * dialog.open();
+ * return dialog.getResult();
+ * </pre>
+ * </p>
+ * @noextend This class is not intended to be subclassed by clients.
+ */
+public class ContainerFileSelectionDialog extends SelectionDialog {
+ // the root file representative to populate the viewer with
+ private FileSystemElement root;
+
+ private IImportStructureProvider structureProvider;
+ static final String FILE_SELECTION_DIALOG = "org.eclipse.ui.ide.file_selection_dialog_context"; //$NON-NLS-1$
+
+ // the visual selection widget group
+ CheckboxTreeAndListGroup selectionGroup;
+
+ // expand all items in the tree view on dialog open
+ private boolean expandAllOnOpen = false;
+
+ // sizing constants
+ private static final int SIZING_SELECTION_WIDGET_WIDTH = 500;
+
+ private static final int SIZING_SELECTION_WIDGET_HEIGHT = 250;
+
+ static final String SELECT_ALL_TITLE = "SelectionDialog_selectLabel"; //$NON-NLS-1$
+ static final String DESELECT_ALL_TITLE = "SelectionDialog_deselectLabel"; //$NON-NLS-1$
+
+ /**
+ * Creates a file selection dialog rooted at the given file system element.
+ *
+ * @param parentShell the parent shell
+ * @param fileSystemElement the root element to populate this dialog with
+ * @param message the message to be displayed at the top of this dialog, or
+ * <code>null</code> to display a default message
+ */
+ public ContainerFileSelectionDialog(Shell parentShell,
+ FileSystemElement fileSystemElement,
+ IImportStructureProvider structureProvider, String message) {
+ super(parentShell);
+ setTitle(Messages.getString("FileSelectionDialog_title")); //$NON-NLS-1$
+ root = fileSystemElement;
+ this.structureProvider = structureProvider;
+ if (message != null) {
+ setMessage(message);
+ } else {
+ setMessage(Messages.getString("FileSelectionDialog_message")); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Add the selection and deselection buttons to the dialog.
+ * @param composite org.eclipse.swt.widgets.Composite
+ */
+ private void addSelectionButtons(Composite composite) {
+
+ Composite buttonComposite = new Composite(composite, SWT.RIGHT);
+ GridLayout layout = new GridLayout();
+ layout.numColumns = 2;
+ buttonComposite.setLayout(layout);
+ GridData data = new GridData(GridData.HORIZONTAL_ALIGN_END);
+ composite.setData(data);
+
+ Button selectButton = new Button(buttonComposite, SWT.PUSH);
+ selectButton.setText(Messages.getString(SELECT_ALL_TITLE));
+ SelectionListener listener = new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ selectionGroup.setAllSelections(true);
+ }
+ };
+ selectButton.addSelectionListener(listener);
+
+ Button deselectButton = new Button(buttonComposite, SWT.PUSH);
+ deselectButton.setText(Messages.getString(DESELECT_ALL_TITLE));
+ listener = new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ selectionGroup.setAllSelections(false);
+
+ }
+ };
+ deselectButton.addSelectionListener(listener);
+
+ }
+
+ /**
+ * Visually checks the previously-specified elements in the container (left)
+ * portion of this dialog's file selection viewer.
+ */
+ private void checkInitialSelections() {
+ @SuppressWarnings("rawtypes")
+ Iterator itemsToCheck = getInitialElementSelections().iterator();
+
+ while (itemsToCheck.hasNext()) {
+ FileSystemElement currentElement = (FileSystemElement) itemsToCheck
+ .next();
+
+ if (currentElement.isDirectory()) {
+ selectionGroup.initialCheckTreeItem(currentElement);
+ } else {
+ selectionGroup.initialCheckListItem(currentElement);
+ }
+ }
+ }
+
+ @Override
+ protected void configureShell(Shell shell) {
+ super.configureShell(shell);
+ PlatformUI.getWorkbench().getHelpSystem().setHelp(shell,
+ FILE_SELECTION_DIALOG);
+ }
+
+ @Override
+ public void create() {
+ super.create();
+ initializeDialog();
+ }
+
+ @Override
+ protected Control createDialogArea(Composite parent) {
+ // page group
+ Composite composite = (Composite) super.createDialogArea(parent);
+
+ createMessageArea(composite);
+
+ // Create a fake parent of the root to be the dialog input element.
+ // Use an empty label so that display of the element's full name
+ // doesn't include a confusing label
+ FileSystemElement input = new FileSystemElement("", null, true);//$NON-NLS-1$
+ input.addChild(root);
+ root.setParent(input);
+
+ selectionGroup = new CheckboxTreeAndListGroup(composite, input,
+ getFolderProvider(), getDynamicFolderProvider(),
+ new WorkbenchLabelProvider(),
+ getFileProvider(), new WorkbenchLabelProvider(), SWT.NONE,
+ SIZING_SELECTION_WIDGET_WIDTH, // since this page has no other significantly-sized
+ SIZING_SELECTION_WIDGET_HEIGHT); // widgets we need to hardcode the combined widget's
+ // size, otherwise it will open too small
+
+ ICheckStateListener listener = event -> getOkButton().setEnabled(
+ selectionGroup.getCheckedElementCount() > 0);
+
+ WorkbenchViewerComparator comparator = new WorkbenchViewerComparator();
+ selectionGroup.setTreeComparator(comparator);
+ selectionGroup.setListComparator(comparator);
+ selectionGroup.addCheckStateListener(listener);
+
+ addSelectionButtons(composite);
+
+ return composite;
+ }
+
+ /**
+ * Returns whether the tree view of the file system element
+ * will be fully expanded when the dialog is opened.
+ *
+ * @return true to expand all on dialog open, false otherwise.
+ */
+ public boolean getExpandAllOnOpen() {
+ return expandAllOnOpen;
+ }
+
+ /**
+ * Returns a content provider for <code>FileSystemElement</code>s that returns
+ * only files as children.
+ */
+ private ITreeContentProvider getFileProvider() {
+ return new WorkbenchContentProvider() {
+ @Override
+ public Object[] getChildren(Object o) {
+ if (o instanceof FileSystemElement) {
+ return ((FileSystemElement) o).getFiles().getChildren(o);
+ }
+
+ return new Object[0];
+ }
+ };
+ }
+
+ /**
+ * Returns a content provider for <code>FileSystemElement</code>s that returns
+ * only folders as children.
+ */
+ private ITreeContentProvider getFolderProvider() {
+ return new WorkbenchContentProvider() {
+ @Override
+ public Object[] getChildren(Object o) {
+ if (o instanceof FileSystemElement) {
+ return ((FileSystemElement) o).getFolders().getChildren(o);
+ }
+
+ return new Object[0];
+ }
+ };
+ }
+
+ /**
+ * Returns a content provider for <code>FileSystemElement</code>s that
+ * returns only folders as children.
+ */
+ private ITreeContentProvider getDynamicFolderProvider() {
+ return new WorkbenchContentProvider() {
+ @Override
+ public Object[] getChildren(Object o) {
+ if (o instanceof MinimizedFileSystemElement) {
+ return ((MinimizedFileSystemElement) o)
+ .getFolders(structureProvider)
+ .getChildren(o);
+ } else if (o instanceof FileSystemElement) {
+ return ((FileSystemElement) o).getFolders().getChildren(o);
+ }
+
+ return new Object[0];
+ }
+ };
+ }
+
+ /**
+ * Initializes this dialog's controls.
+ */
+ private void initializeDialog() {
+ // initialize page
+ if (getInitialElementSelections().isEmpty()) {
+ getOkButton().setEnabled(false);
+ } else {
+ checkInitialSelections();
+ }
+ selectionGroup.aboutToOpen();
+ if (expandAllOnOpen) {
+ selectionGroup.expandAll();
+ }
+ }
+
+ /**
+ * The <code>FileSelectionDialog</code> implementation of this
+ * <code>Dialog</code> method builds a list of the selected files for later
+ * retrieval by the client and closes this dialog.
+ */
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ @Override
+ protected void okPressed() {
+ Iterator resultEnum = selectionGroup.getAllCheckedListItems();
+ ArrayList list = new ArrayList();
+ while (resultEnum.hasNext()) {
+ list.add(resultEnum.next());
+ }
+ setResult(list);
+ super.okPressed();
+ }
+
+ /**
+ * Set whether the tree view of the file system element
+ * will be fully expanded when the dialog is opened.
+ *
+ * @param expandAll true to expand all on dialog open, false otherwise.
+ */
+ public void setExpandAllOnOpen(boolean expandAll) {
+ expandAllOnOpen = expandAll;
+ }
+}
diff --git a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/ContainerFileSystemProvider.java b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/ContainerFileSystemProvider.java
new file mode 100644
index 0000000..7f49d33
--- /dev/null
+++ b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/ContainerFileSystemProvider.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Red Hat 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:
+ * Red Hat - Initial Contribution
+ *******************************************************************************/
+package org.eclipse.linuxtools.internal.docker.ui;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.linuxtools.docker.core.DockerException;
+import org.eclipse.linuxtools.docker.core.IDockerConnection;
+import org.eclipse.linuxtools.internal.docker.core.ContainerFileProxy;
+import org.eclipse.linuxtools.internal.docker.core.DockerConnection;
+import org.eclipse.ui.wizards.datatransfer.IImportStructureProvider;
+
+public class ContainerFileSystemProvider implements IImportStructureProvider {
+
+ private final IDockerConnection connection;
+ private final String containerId;
+
+ public ContainerFileSystemProvider(IDockerConnection connection,
+ String containerId) {
+ this.connection = connection;
+ this.containerId = containerId;
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public List getChildren(Object element) {
+ try {
+ ContainerFileProxy proxy = (ContainerFileProxy) element;
+ if (proxy.isFolder()) {
+ return ((DockerConnection) connection).readContainerDirectory(
+ containerId,
+ proxy.getFullPath());
+ }
+ } catch (DockerException e) {
+ // do nothing for now
+ }
+ return new ArrayList<ContainerFileProxy>();
+ }
+
+ @Override
+ public InputStream getContents(Object element) {
+ return null; // we do not have the contents of container file
+ }
+
+ @Override
+ public String getFullPath(Object element) {
+ return ((ContainerFileProxy) element).getFullPath();
+ }
+
+ @Override
+ public String getLabel(Object element) {
+ return ((ContainerFileProxy) element).getLabel();
+ }
+
+ @Override
+ public boolean isFolder(Object element) {
+ return ((ContainerFileProxy) element).isFolder();
+ }
+
+}
diff --git a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/DynamicCheckboxTreeViewer.java b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/DynamicCheckboxTreeViewer.java
new file mode 100644
index 0000000..e52081c
--- /dev/null
+++ b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/DynamicCheckboxTreeViewer.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Red Hat 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:
+ * Red Hat - Initial Contribution
+ *******************************************************************************/
+package org.eclipse.linuxtools.internal.docker.ui;
+
+import org.eclipse.jface.viewers.CheckboxTreeViewer;
+import org.eclipse.jface.viewers.IContentProvider;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.swt.events.TreeEvent;
+import org.eclipse.swt.widgets.Tree;
+
+public class DynamicCheckboxTreeViewer extends CheckboxTreeViewer {
+
+ private final ITreeContentProvider dynamicProvider;
+ private boolean useDynamic;
+
+ public DynamicCheckboxTreeViewer(Tree tree,
+ ITreeContentProvider dynamicProvider) {
+ super(tree);
+ this.dynamicProvider = dynamicProvider;
+ }
+
+ public void useDynamic(boolean value) {
+ this.useDynamic = value;
+ }
+
+ @Override
+ public IContentProvider getContentProvider() {
+ if (useDynamic) {
+ return dynamicProvider;
+ }
+ return super.getContentProvider();
+ }
+
+ @Override
+ protected void handleTreeExpand(TreeEvent event) {
+ useDynamic(true);
+ super.handleTreeExpand(event);
+ useDynamic(false);
+ }
+
+}
diff --git a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/Messages.java b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/Messages.java
new file mode 100644
index 0000000..345dde3
--- /dev/null
+++ b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/Messages.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Red Hat.
+ * 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:
+ * Red Hat - Initial Contribution
+ *******************************************************************************/
+package org.eclipse.linuxtools.internal.docker.ui;
+
+import java.text.MessageFormat;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+public class Messages {
+
+ private static final String BUNDLE_NAME = Messages.class.getName();
+
+ public static String getString(String key) {
+ try {
+ return ResourceBundle.getBundle(BUNDLE_NAME).getString(key);
+ } catch (MissingResourceException e) {
+ return '!' + key + '!';
+ } catch (NullPointerException e) {
+ return '#' + key + '#';
+ }
+ }
+
+ public static String getFormattedString(String key, String arg) {
+ return MessageFormat.format(getString(key), new Object[] { arg });
+ }
+
+ public static String getFormattedString(String key, String[] args) {
+ return MessageFormat.format(getString(key), (Object[]) args);
+ }
+
+}
diff --git a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/Messages.properties b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/Messages.properties
new file mode 100644
index 0000000..9cf863a
--- /dev/null
+++ b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/Messages.properties
@@ -0,0 +1,6 @@
+
+TarImport_invalid_tar_format = Not a valid tar format
+FileSelectionDialog_title = File Selection
+FileSelectionDialog_message = Select the files:
+SelectionDialog_selectLabel = &Select All
+SelectionDialog_deselectLabel = &Deselect All
diff --git a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/MinimizedFileSystemElement.java b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/MinimizedFileSystemElement.java
new file mode 100644
index 0000000..1eb503d
--- /dev/null
+++ b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/MinimizedFileSystemElement.java
@@ -0,0 +1,109 @@
+package org.eclipse.linuxtools.internal.docker.ui;
+
+/*******************************************************************************
+ * Copyright (c) 2000, 2009, 2016 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
+ * Red Hat Inc. - modified to use with Docker Tooling
+ *******************************************************************************/
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.ui.dialogs.FileSystemElement;
+import org.eclipse.ui.model.AdaptableList;
+import org.eclipse.ui.wizards.datatransfer.IImportStructureProvider;
+
+/**
+ * The <code>MinimizedFileSystemElement</code> is a
+ * <code>FileSystemElement</code> that knows if it has been populated or not.
+ */
+public class MinimizedFileSystemElement extends FileSystemElement {
+ private boolean populated = false;
+
+ /**
+ * Create a <code>MinimizedFileSystemElement</code> with the supplied name
+ * and parent.
+ *
+ * @param name
+ * the name of the file element this represents
+ * @param parent
+ * the containing parent
+ * @param isDirectory
+ * indicated if this could have children or not
+ */
+ public MinimizedFileSystemElement(String name, FileSystemElement parent,
+ boolean isDirectory) {
+ super(name, parent, isDirectory);
+ }
+
+ /**
+ * Returns a list of the files that are immediate children. Use the supplied
+ * provider if it needs to be populated. of this folder.
+ */
+ public AdaptableList getFiles(IImportStructureProvider provider) {
+ if (!populated) {
+ populate(provider);
+ }
+ return super.getFiles();
+ }
+
+ /**
+ * Returns a list of the folders that are immediate children. Use the
+ * supplied provider if it needs to be populated. of this folder.
+ */
+ public AdaptableList getFolders(IImportStructureProvider provider) {
+ if (!populated) {
+ populate(provider);
+ }
+ return super.getFolders();
+ }
+
+ /**
+ * Return whether or not population has happened for the receiver.
+ */
+ boolean isPopulated() {
+ return this.populated;
+ }
+
+ /**
+ * Populate the files and folders of the receiver using the supplied
+ * structure provider.
+ *
+ * @param provider
+ * org.eclipse.ui.wizards.datatransfer.IImportStructureProvider
+ */
+ private void populate(IImportStructureProvider provider) {
+
+ Object fileSystemObject = getFileSystemObject();
+
+ @SuppressWarnings("rawtypes")
+ List children = provider.getChildren(fileSystemObject);
+ if (children != null) {
+ @SuppressWarnings("rawtypes")
+ Iterator childrenEnum = children.iterator();
+ while (childrenEnum.hasNext()) {
+ Object child = childrenEnum.next();
+
+ String elementLabel = provider.getLabel(child);
+ // Create one level below
+ MinimizedFileSystemElement result = new MinimizedFileSystemElement(
+ elementLabel, this, provider.isFolder(child));
+ result.setFileSystemObject(child);
+ }
+ }
+ setPopulated();
+ }
+
+ /**
+ * Set whether or not population has happened for the receiver to true.
+ */
+ public void setPopulated() {
+ this.populated = true;
+ }
+}
diff --git a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/PopulateContainerFilesOperation.java b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/PopulateContainerFilesOperation.java
new file mode 100644
index 0000000..5e688e4
--- /dev/null
+++ b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/PopulateContainerFilesOperation.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Red Hat 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:
+ * Red Hat - Initial Contribution
+ *******************************************************************************/
+package org.eclipse.linuxtools.internal.docker.ui;
+
+import org.eclipse.ui.dialogs.FileSystemElement;
+import org.eclipse.ui.wizards.datatransfer.IImportStructureProvider;
+
+public class PopulateContainerFilesOperation extends PopulateRootOperation {
+
+ private final FileSystemElement rootParent;
+
+ public PopulateContainerFilesOperation(Object rootObject,
+ FileSystemElement rootParent,
+ IImportStructureProvider structureProvider) {
+ super(rootObject, structureProvider);
+ this.rootParent = rootParent;
+ }
+
+ @Override
+ protected FileSystemElement createElement(FileSystemElement parent,
+ Object fileSystemObject) throws InterruptedException {
+ FileSystemElement element = (parent == null ? this.rootParent : parent);
+ return super.createElement(element, fileSystemObject);
+ }
+}
diff --git a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/PopulateRootOperation.java b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/PopulateRootOperation.java
new file mode 100644
index 0000000..7f79328
--- /dev/null
+++ b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/PopulateRootOperation.java
@@ -0,0 +1,90 @@
+package org.eclipse.linuxtools.internal.docker.ui;
+/*******************************************************************************
+ * Copyright (c) 2000, 2014, 2016 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
+ * Red Hat Inc. - modified to use with Docker Tooling
+ *******************************************************************************/
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.jface.operation.ModalContext;
+import org.eclipse.ui.dialogs.FileSystemElement;
+import org.eclipse.ui.wizards.datatransfer.IImportStructureProvider;
+
+/**
+ * The PopulateFilesOperation is an operation used to populate a
+ * FileSystemElement one level deep rather than the whole way.
+ */
+public class PopulateRootOperation extends SelectFilesOperation {
+ /**
+ * Create a new <code>PopulateFilesOperation</code>.
+ *
+ * @param rootObject
+ * the object to be populated
+ * @param structureProvider
+ * the object that defines how we are to populate it.
+ */
+ public PopulateRootOperation(Object rootObject,
+ IImportStructureProvider structureProvider) {
+ super(rootObject, structureProvider);
+ }
+
+ /**
+ * Creates and returns a <code>FileSystemElement</code> if the specified
+ * file system object merits one. The criteria for this are: - if the file
+ * system object is a container then it must have either a child container
+ * or an associated file - if the file system object is a file then it must
+ * have an extension suitable for selection
+ */
+ @Override
+ protected FileSystemElement createElement(FileSystemElement parent,
+ Object fileSystemObject) throws InterruptedException {
+
+ // Iterate on level deep
+ return createElement(parent, fileSystemObject, 2);
+
+ }
+
+ /**
+ * Creates and returns a <code>FileSystemElement</code> if the specified
+ * file system object merits one. The criteria for this are: - if the file
+ * system object is a container then it must have either a child container
+ * or an associated file - if the file system object is a file then it must
+ * have an extension suitable for selection recurse down for depth to
+ * populate children
+ */
+ @SuppressWarnings("rawtypes")
+ protected FileSystemElement createElement(FileSystemElement parent,
+ Object fileSystemObject, int depth) throws InterruptedException {
+ ModalContext.checkCanceled(monitor);
+ boolean isContainer = provider.isFolder(fileSystemObject);
+ String elementLabel = parent == null
+ ? provider.getFullPath(fileSystemObject)
+ : provider.getLabel(fileSystemObject);
+
+ MinimizedFileSystemElement result = new MinimizedFileSystemElement(
+ elementLabel, parent, isContainer);
+ result.setFileSystemObject(fileSystemObject);
+
+ if (isContainer && depth > 0) {
+ List children = provider.getChildren(fileSystemObject);
+ if (children != null) {
+ Iterator childrenEnum = children.iterator();
+ while (childrenEnum.hasNext()) {
+ createElement(result, childrenEnum.next(), depth - 1);
+ }
+ }
+ result.setPopulated();
+ }
+
+
+ return result;
+ }
+}
diff --git a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/SelectFilesOperation.java b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/SelectFilesOperation.java
new file mode 100644
index 0000000..9f4a099
--- /dev/null
+++ b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/SelectFilesOperation.java
@@ -0,0 +1,171 @@
+package org.eclipse.linuxtools.internal.docker.ui;
+/*******************************************************************************
+ * Copyright (c) 2000, 2014, 2016 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
+ * Red Hat Inc. - copied to Docker Tooling
+ *******************************************************************************/
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.jface.operation.ModalContext;
+import org.eclipse.linuxtools.internal.docker.ui.jobs.JobMessages;
+import org.eclipse.ui.dialogs.FileSystemElement;
+import org.eclipse.ui.wizards.datatransfer.IImportStructureProvider;
+
+/**
+ * Operation responsible for traversing a specified file system position
+ * recursively and building - a tree that represents the container structure - a
+ * collection containing all files meeting a specified extension criteria
+ *
+ * This is implemented as an Operation in order to provide an escape to the user
+ * (the Cancel button) if the operation drags on for too long
+ */
+public class SelectFilesOperation implements IRunnableWithProgress {
+ IProgressMonitor monitor;
+
+ Object root;
+
+ IImportStructureProvider provider;
+
+ String desiredExtensions[];
+
+ FileSystemElement result;
+
+ private static String POPULATING_CONTAINER_FILES = "PopulateContainerFiles.msg"; //$NON-NLS-1$
+
+ /**
+ * Creates a new <code>SelectFilesOperation</code>.
+ */
+ public SelectFilesOperation(Object rootObject,
+ IImportStructureProvider structureProvider) {
+ super();
+ root = rootObject;
+ provider = structureProvider;
+ }
+
+ /**
+ * Creates and returns a <code>FileSystemElement</code> if the specified
+ * file system object merits one. The criteria for this are: - if the file
+ * system object is a container then it must have either a child container
+ * or an associated file - if the file system object is a file then it must
+ * have an extension suitable for selection
+ */
+ @SuppressWarnings("rawtypes")
+ protected FileSystemElement createElement(FileSystemElement parent,
+ Object fileSystemObject) throws InterruptedException {
+ ModalContext.checkCanceled(monitor);
+ boolean isContainer = provider.isFolder(fileSystemObject);
+ String elementLabel = parent == null
+ ? provider.getFullPath(fileSystemObject)
+ : provider.getLabel(fileSystemObject);
+
+ if (!isContainer && !hasDesiredExtension(elementLabel)) {
+ return null;
+ }
+
+ FileSystemElement result = new FileSystemElement(elementLabel, parent,
+ isContainer);
+ result.setFileSystemObject(fileSystemObject);
+
+ if (isContainer) {
+ boolean haveChildOrFile = false;
+ List children = provider.getChildren(fileSystemObject);
+ if (children == null) {
+ children = new ArrayList(1);
+ }
+ Iterator childrenEnum = children.iterator();
+ while (childrenEnum.hasNext()) {
+ if (createElement(result, childrenEnum.next()) != null) {
+ haveChildOrFile = true;
+ }
+ }
+
+ if (!haveChildOrFile && parent != null) {
+ parent.removeFolder(result);
+ result = null;
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns the extension portion of the passed filename string.
+ */
+ protected String getExtensionFor(String filename) {
+ int nIndex = filename.lastIndexOf('.');
+
+ if (nIndex >= 0) {
+ return filename.substring(nIndex + 1);
+ }
+
+ return "";//$NON-NLS-1$
+
+ }
+
+ /**
+ * Returns the resulting root file system element.
+ */
+ public FileSystemElement getResult() {
+ return result;
+ }
+
+ /**
+ * Returns a boolean indicating whether the extension of the passed filename
+ * is one of the extensions specified as desired by the filter.
+ */
+ protected boolean hasDesiredExtension(String filename) {
+ if (desiredExtensions == null) {
+ return true;
+ }
+
+ int extensionsSize = desiredExtensions.length;
+ for (int i = 0; i < extensionsSize; i++) {
+ if (getExtensionFor(filename)
+ .equalsIgnoreCase(desiredExtensions[i])) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Runs the operation.
+ */
+ @Override
+ public void run(IProgressMonitor monitor) throws InterruptedException {
+ try {
+ this.monitor = monitor;
+ monitor.beginTask(
+ JobMessages.getString(POPULATING_CONTAINER_FILES),
+ IProgressMonitor.UNKNOWN);
+ result = createElement(null, root);
+ if (result == null) {
+ result = new FileSystemElement(provider.getLabel(root), null,
+ provider.isFolder(root));
+ result.setFileSystemObject(root);
+ }
+ } finally {
+ monitor.done();
+ }
+ }
+
+ /**
+ * Sets the file extensions which are desired. A value of <code>null</code>
+ * indicates that all files should be kept regardless of extension.
+ */
+ public void setDesiredExtensions(String[] extensions) {
+ desiredExtensions = extensions;
+ }
+}
diff --git a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/TarEntry.java b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/TarEntry.java
new file mode 100644
index 0000000..ef4d54d
--- /dev/null
+++ b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/TarEntry.java
@@ -0,0 +1,145 @@
+package org.eclipse.linuxtools.internal.docker.ui;
+
+/*******************************************************************************
+ * Copyright (c) 2004, 2015, 2016 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
+ * Red Hat Inc. - copied over to Docker Tooling
+ *******************************************************************************/
+
+/**
+ * Representation of a file in a tar archive.
+ *
+ */
+public class TarEntry implements Cloneable {
+ private String name;
+ private long mode, time, size;
+ private int type;
+ int filepos;
+
+ /**
+ * Entry type for normal files.
+ */
+ public static final int FILE = '0';
+
+ /**
+ * Entry type for directories.
+ */
+ public static final int DIRECTORY = '5';
+
+ /**
+ * Create a new TarEntry for a file of the given name at the given position
+ * in the file.
+ *
+ * @param name
+ * filename
+ * @param pos
+ * position in the file in bytes
+ */
+ TarEntry(String name, int pos) {
+ this.name = name;
+ mode = 0644;
+ type = FILE;
+ filepos = pos;
+ time = System.currentTimeMillis() / 1000;
+ }
+
+ /**
+ * Create a new TarEntry for a file of the given name.
+ *
+ * @param name
+ * filename
+ */
+ public TarEntry(String name) {
+ this(name, -1);
+ }
+
+ /**
+ * Returns the type of this file, one of FILE, LINK, SYM_LINK, CHAR_DEVICE,
+ * BLOCK_DEVICE, DIRECTORY or FIFO.
+ *
+ * @return file type
+ */
+ public int getFileType() {
+ return type;
+ }
+
+ /**
+ * Returns the mode of the file in UNIX permissions format.
+ *
+ * @return file mode
+ */
+ public long getMode() {
+ return mode;
+ }
+
+ /**
+ * Returns the name of the file.
+ *
+ * @return filename
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the size of the file in bytes.
+ *
+ * @return filesize
+ */
+ public long getSize() {
+ return size;
+ }
+
+ /**
+ * Returns the modification time of the file in seconds since January 1st
+ * 1970.
+ *
+ * @return time
+ */
+ public long getTime() {
+ return time;
+ }
+
+ /**
+ * Sets the type of the file, one of FILE, LINK, SYMLINK, CHAR_DEVICE,
+ * BLOCK_DEVICE, or DIRECTORY.
+ *
+ * @param type
+ */
+ public void setFileType(int type) {
+ this.type = type;
+ }
+
+ /**
+ * Sets the mode of the file in UNIX permissions format.
+ *
+ * @param mode
+ */
+ public void setMode(long mode) {
+ this.mode = mode;
+ }
+
+ /**
+ * Sets the size of the file in bytes.
+ *
+ * @param size
+ */
+ public void setSize(long size) {
+ this.size = size;
+ }
+
+ /**
+ * Sets the modification time of the file in seconds since January 1st 1970.
+ *
+ * @param time
+ */
+ public void setTime(long time) {
+ this.time = time;
+ }
+}
diff --git a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/TarException.java b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/TarException.java
new file mode 100644
index 0000000..ae1bc8b
--- /dev/null
+++ b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/TarException.java
@@ -0,0 +1,44 @@
+package org.eclipse.linuxtools.internal.docker.ui;
+/*******************************************************************************
+ * Copyright (c) 2004, 2015, 2016 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
+ * Red Hat Inc. - used in Docker Tooling
+ *******************************************************************************/
+
+/**
+ * Exception generated upon encountering corrupted tar files.
+ */
+public class TarException extends Exception {
+ /**
+ * Generated serial version UID for this class.
+ */
+ private static final long serialVersionUID = 2886671254518853528L;
+
+ /**
+ * Constructs a TarException with the specified detail string.
+ *
+ * @param s
+ * the detail string
+ */
+ public TarException(String s) {
+ super(s);
+ }
+
+ /**
+ * Constructs a TarException with the specified detail string.
+ *
+ * @param s
+ * the detail string
+ * @param cause
+ * the cause
+ */
+ public TarException(String s, Throwable cause) {
+ super(s, cause);
+ }
+}
diff --git a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/TarInputStream.java b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/TarInputStream.java
new file mode 100644
index 0000000..4da621e
--- /dev/null
+++ b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/TarInputStream.java
@@ -0,0 +1,347 @@
+package org.eclipse.linuxtools.internal.docker.ui;
+/*******************************************************************************
+ * Copyright (c) 2004, 2015, 2016 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
+ * Red Hat Inc. - modification to use in Docker UI plug-in
+ *******************************************************************************/
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Input stream for reading files in ustar format (tar) compatible with the
+ * specification in IEEE Std 1003.1-2001. Also supports long filenames encoded
+ * using the GNU @LongLink extension.
+ */
+public class TarInputStream extends FilterInputStream {
+ private final static String TarImport_invalid_tar_format = "TarImport_invalid_tar_format"; //$NON-NLS-1$
+ private int nextEntry = 0;
+ private int nextEOF = 0;
+ private int filepos = 0;
+ private int bytesread = 0;
+ private TarEntry firstEntry = null;
+ private String longLinkName = null;
+
+ /**
+ * Creates a new tar input stream on the given input stream.
+ *
+ * @param in
+ * input stream
+ * @throws TarException
+ * @throws IOException
+ */
+ public TarInputStream(InputStream in) throws TarException, IOException {
+ super(in);
+
+ // Read in the first TarEntry to make sure
+ // the input is a valid tar file stream.
+ firstEntry = getNextEntry();
+ }
+
+ /**
+ * Create a new tar input stream, skipping ahead to the given entry in the
+ * file.
+ *
+ * @param in
+ * input stream
+ * @param entry
+ * skips to this entry in the file
+ * @throws TarException
+ * @throws IOException
+ */
+ TarInputStream(InputStream in, TarEntry entry)
+ throws TarException, IOException {
+ super(in);
+ skipToEntry(entry);
+ }
+
+ /**
+ * The checksum of a tar file header is simply the sum of the bytes in the
+ * header.
+ *
+ * @param header
+ * @return checksum
+ */
+ private long headerChecksum(byte[] header) {
+ long sum = 0;
+ for (int i = 0; i < 512; i++) {
+ sum += header[i] & 0xff;
+ }
+ return sum;
+ }
+
+ /**
+ * Skips ahead to the position of the given entry in the file.
+ *
+ * @param entry
+ * @returns false if the entry has already been passed
+ * @throws TarException
+ * @throws IOException
+ */
+ boolean skipToEntry(TarEntry entry) throws TarException, IOException {
+ int bytestoskip = entry.filepos - bytesread;
+ if (bytestoskip < 0) {
+ return false;
+ }
+ while (bytestoskip > 0) {
+ long ret = in.skip(bytestoskip);
+ if (ret < 0) {
+ throw new IOException("early end of stream"); //$NON-NLS-1$
+ }
+ bytestoskip -= ret;
+ bytesread += ret;
+ }
+ filepos = entry.filepos;
+ nextEntry = 0;
+ nextEOF = 0;
+ // Read next header to seek to file data.
+ getNextEntry();
+ return true;
+ }
+
+ /**
+ * Returns true if the header checksum is correct.
+ *
+ * @param header
+ * @return true if this header has a valid checksum
+ */
+ private boolean isValidTarHeader(byte[] header) {
+ long fileChecksum, calculatedChecksum;
+ int pos, i;
+
+ pos = 148;
+ StringBuffer checksumString = new StringBuffer();
+ for (i = 0; i < 8; i++) {
+ if (header[pos + i] == ' ') {
+ continue;
+ }
+ if (header[pos + i] == 0
+ || !Character.isDigit((char) header[pos + i])) {
+ break;
+ }
+ checksumString.append((char) header[pos + i]);
+ }
+ if (checksumString.length() == 0) {
+ return false;
+ }
+ if (checksumString.charAt(0) != '0') {
+ checksumString.insert(0, '0');
+ }
+ try {
+ fileChecksum = Long.decode(checksumString.toString()).longValue();
+ } catch (NumberFormatException exception) {
+ // This is not valid if it cannot be parsed
+ return false;
+ }
+
+ // Blank out the checksum.
+ for (i = 0; i < 8; i++) {
+ header[pos + i] = ' ';
+ }
+ calculatedChecksum = headerChecksum(header);
+
+ return (fileChecksum == calculatedChecksum);
+ }
+
+ /**
+ * Returns the next entry in the tar file. Does not handle GNU @LongLink
+ * extensions.
+ *
+ * @return the next entry in the tar file
+ * @throws TarException
+ * @throws IOException
+ */
+ TarEntry getNextEntryInternal() throws TarException, IOException {
+ byte[] header = new byte[512];
+ int pos = 0;
+ int i;
+
+ if (firstEntry != null) {
+ TarEntry entryReturn = firstEntry;
+ firstEntry = null;
+ return entryReturn;
+ }
+
+ while (nextEntry > 0) {
+ long ret = in.skip(nextEntry);
+ if (ret < 0) {
+ throw new IOException("early end of stream"); //$NON-NLS-1$
+ }
+ nextEntry -= ret;
+ bytesread += ret;
+ }
+
+ int bytestoread = 512;
+ while (bytestoread > 0) {
+ int ret = super.read(header, 512 - bytestoread, bytestoread);
+ if (ret < 0) {
+ throw new IOException("early end of stream"); //$NON-NLS-1$
+ }
+ bytestoread -= ret;
+ bytesread += ret;
+ }
+
+ // If we have a header of all zeros, this marks the end of the file.
+ if (headerChecksum(header) == 0) {
+ // We are at the end of the file.
+ if (filepos > 0) {
+ return null;
+ }
+
+ // Invalid stream.
+ throw new TarException("not in tar format"); //$NON-NLS-1$
+ }
+
+ // Validate checksum.
+ if (!isValidTarHeader(header)) {
+ throw new TarException("not in tar format"); //$NON-NLS-1$
+ }
+
+ while (pos < 100 && header[pos] != 0) {
+ pos++;
+ }
+ String name = new String(header, 0, pos, "UTF8"); //$NON-NLS-1$
+ // Prepend the prefix here.
+ pos = 345;
+ if (header[pos] != 0) {
+ while (pos < 500 && header[pos] != 0) {
+ pos++;
+ }
+ String prefix = new String(header, 345, pos - 345, "UTF8"); //$NON-NLS-1$
+ name = prefix + "/" + name; //$NON-NLS-1$
+ }
+
+ TarEntry entry;
+ if (longLinkName != null) {
+ entry = new TarEntry(longLinkName, filepos);
+ longLinkName = null;
+ } else {
+ entry = new TarEntry(name, filepos);
+ }
+ if (header[156] != 0) {
+ entry.setFileType(header[156]);
+ }
+
+ pos = 100;
+ StringBuffer mode = new StringBuffer();
+ for (i = 0; i < 8; i++) {
+ if (header[pos + i] == 0) {
+ break;
+ }
+ if (header[pos + i] == ' ') {
+ continue;
+ }
+ mode.append((char) header[pos + i]);
+ }
+ if (mode.length() > 0 && mode.charAt(0) != '0') {
+ mode.insert(0, '0');
+ }
+ try {
+ long fileMode = Long.decode(mode.toString()).longValue();
+ entry.setMode(fileMode);
+ } catch (NumberFormatException nfe) {
+ throw new TarException(
+ Messages.getString(TarImport_invalid_tar_format), nfe);
+ }
+
+ pos = 100 + 24;
+ StringBuffer size = new StringBuffer();
+ for (i = 0; i < 12; i++) {
+ if (header[pos + i] == 0) {
+ break;
+ }
+ if (header[pos + i] == ' ') {
+ continue;
+ }
+ size.append((char) header[pos + i]);
+ }
+ if (size.charAt(0) != '0') {
+ size.insert(0, '0');
+ }
+ int fileSize;
+ try {
+ fileSize = Integer.decode(size.toString()).intValue();
+ } catch (NumberFormatException nfe) {
+ throw new TarException(
+ Messages.getString(TarImport_invalid_tar_format), nfe);
+ }
+
+ entry.setSize(fileSize);
+ nextEOF = fileSize;
+ if (fileSize % 512 > 0) {
+ nextEntry = fileSize + (512 - (fileSize % 512));
+ } else {
+ nextEntry = fileSize;
+ }
+ filepos += (nextEntry + 512);
+ return entry;
+ }
+
+ /**
+ * Moves ahead to the next file in the tar archive and returns a TarEntry
+ * object describing it.
+ *
+ * @return the next entry in the tar file
+ * @throws TarException
+ * @throws IOException
+ */
+ public TarEntry getNextEntry() throws TarException, IOException {
+ TarEntry entry = getNextEntryInternal();
+
+ if (entry != null && entry.getName().equals("././@LongLink")) { //$NON-NLS-1$
+ // This is a GNU extension for doing long filenames.
+ // We get a file called ././@LongLink which just contains
+ // the real pathname.
+ byte[] longNameData = new byte[(int) entry.getSize()];
+ int bytesread = 0;
+ while (bytesread < longNameData.length) {
+ int cur = read(longNameData, bytesread,
+ longNameData.length - bytesread);
+ if (cur < 0) {
+ throw new IOException("early end of stream"); //$NON-NLS-1$
+ }
+ bytesread += cur;
+ }
+
+ int pos = 0;
+ while (pos < longNameData.length && longNameData[pos] != 0) {
+ pos++;
+ }
+ longLinkName = new String(longNameData, 0, pos, "UTF8"); //$NON-NLS-1$
+ return getNextEntryInternal();
+ }
+ return entry;
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (nextEOF == 0) {
+ return -1;
+ }
+ if (len > nextEOF) {
+ len = nextEOF;
+ }
+ int size = super.read(b, off, len);
+ nextEntry -= size;
+ nextEOF -= size;
+ bytesread += size;
+ return size;
+ }
+
+ @Override
+ public int read() throws IOException {
+ byte[] data = new byte[1];
+ int size = read(data, 0, 1);
+ if (size < 0) {
+ return size;
+ }
+ return data[0];
+ }
+}
diff --git a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/commands/CommandMessages.properties b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/commands/CommandMessages.properties
index 981ba58..f5c96a0 100644
--- a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/commands/CommandMessages.properties
+++ b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/commands/CommandMessages.properties
@@ -10,7 +10,13 @@
command.showIn.systemExplorer.failure.execute=Failed to execute {0}. Return code was: {1}
missing_connection=Missing connection
-command.pullImage.failure.no_connection=Unable to pull an image: no connection was not found in the selection.
+command.pullImage.failure.no_connection=Unable to pull an image: no connection was found in the selection.
+
+command.copyfromcontainer.failure.no_connection=Unable to copy from image: no conection was found.
+command.copyfromcontainer.job.title=Copying files from {0}
+command.copyfromcontainer.job.task=Copying files
+command.copyfromcontainer.job.subtask=Copying {0}
+command.copyfromcontainer.error.msg=Error copying [{0}] from <{1}>
command.enableconnection=Opening connection to {0}
command.enableconnection.failure=Failed to connect to {0}
diff --git a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/commands/CopyFromContainerCommandHandler.java b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/commands/CopyFromContainerCommandHandler.java
new file mode 100644
index 0000000..0161625
--- /dev/null
+++ b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/commands/CopyFromContainerCommandHandler.java
@@ -0,0 +1,168 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Red Hat 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:
+ * Red Hat - Initial Contribution
+ *******************************************************************************/
+package org.eclipse.linuxtools.internal.docker.ui.commands;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.linuxtools.docker.core.DockerException;
+import org.eclipse.linuxtools.docker.core.IDockerConnection;
+import org.eclipse.linuxtools.docker.core.IDockerContainer;
+import org.eclipse.linuxtools.docker.ui.Activator;
+import org.eclipse.linuxtools.docker.ui.wizards.ImageSearch;
+import org.eclipse.linuxtools.internal.docker.core.ContainerFileProxy;
+import org.eclipse.linuxtools.internal.docker.core.DockerConnection;
+import org.eclipse.linuxtools.internal.docker.ui.TarEntry;
+import org.eclipse.linuxtools.internal.docker.ui.TarException;
+import org.eclipse.linuxtools.internal.docker.ui.TarInputStream;
+import org.eclipse.linuxtools.internal.docker.ui.wizards.ContainerCopyFrom;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+/**
+ * Command handler that opens the {@link ImageSearch} wizard and pulls the
+ * selected image in background on completion.
+ *
+ */
+public class CopyFromContainerCommandHandler extends AbstractHandler {
+
+ private static final String ERROR_COPYING_FROM_CONTAINER_NO_CONNECTION = "command.copyfromcontainer.failure.no_connection"; //$NON-NLS-1$
+ private static final String MISSING_CONNECTION = "missing_connection"; //$NON-NLS-1$
+ private static final String ERROR_COPYING_FROM_CONTAINER = "command.copyfromcontainer.error.msg"; //$NON-NLS-1$
+ private static final String COPY_FROM_CONTAINER_JOB_TASK = "command.copyfromcontainer.job.task"; //$NON-NLS-1$
+ private static final String COPY_FROM_CONTAINER_JOB_TITLE = "command.copyfromcontainer.job.title"; //$NON-NLS-1$
+ private static final String COPY_FROM_CONTAINER_JOB_SUBTASK = "command.copyfromcontainer.job.subtask"; //$NON-NLS-1$
+
+ @Override
+ public Object execute(final ExecutionEvent event) {
+ final IWorkbenchPart activePart = HandlerUtil.getActivePart(event);
+ final IDockerConnection connection = CommandUtils
+ .getCurrentConnection(activePart);
+ final List<IDockerContainer> selectedContainers = CommandUtils
+ .getSelectedContainers(activePart);
+ if (selectedContainers.size() != 1) {
+ return null;
+ }
+ final IDockerContainer container = selectedContainers.get(0);
+ if (connection == null) {
+ MessageDialog.openError(
+ PlatformUI.getWorkbench().getActiveWorkbenchWindow()
+ .getShell(),
+ CommandMessages.getString(MISSING_CONNECTION),
+ CommandMessages
+ .getString(
+ ERROR_COPYING_FROM_CONTAINER_NO_CONNECTION));
+ } else {
+ final ContainerCopyFrom wizard = new ContainerCopyFrom(connection,
+ container);
+ final boolean copyFromContainer = CommandUtils.openWizard(wizard,
+ HandlerUtil.getActiveShell(event));
+ if (copyFromContainer) {
+ performCopyFromContainer(connection, container,
+ wizard.getTarget(), wizard.getSources());
+ }
+ }
+ return null;
+ }
+
+ private void performCopyFromContainer(final IDockerConnection connection,
+ final IDockerContainer container, final String target,
+ final List<ContainerFileProxy> files) {
+ final Job copyFromContainerJob = new Job(
+ CommandMessages.getFormattedString(
+ COPY_FROM_CONTAINER_JOB_TITLE, container.name())) {
+
+ @Override
+ protected IStatus run(final IProgressMonitor monitor) {
+ monitor.beginTask(
+ CommandMessages.getString(COPY_FROM_CONTAINER_JOB_TASK),
+ files.size());
+ try {
+ for (ContainerFileProxy proxy : files) {
+ if (monitor.isCanceled()) {
+ monitor.done();
+ return Status.CANCEL_STATUS;
+ }
+ try {
+ monitor.setTaskName(
+ CommandMessages.getFormattedString(
+ COPY_FROM_CONTAINER_JOB_SUBTASK,
+ proxy.getFullPath()));
+ monitor.worked(1);
+ TarInputStream k = new TarInputStream(
+ ((DockerConnection) connection)
+ .copyContainer(container.id(),
+ proxy.getLink()));
+ TarEntry te = k.getNextEntry();
+ long size = te.getSize();
+ IPath path = new Path(target);
+ path = path.append(proxy.getName());
+ File f = new File(path.toOSString());
+ f.createNewFile();
+ FileOutputStream os = new FileOutputStream(f);
+ if (size > 4096)
+ size = 4096;
+ byte[] barray = new byte[(int) size];
+ while (k.read(barray) > 0) {
+ if (monitor.isCanceled()) {
+ monitor.done();
+ k.close();
+ os.close();
+ return Status.CANCEL_STATUS;
+ }
+ os.write(barray);
+ }
+ k.close();
+ os.close();
+ } catch (final DockerException e) {
+ Display.getDefault()
+ .syncExec(() -> MessageDialog.openError(
+ PlatformUI.getWorkbench()
+ .getActiveWorkbenchWindow()
+ .getShell(),
+ CommandMessages.getFormattedString(
+ ERROR_COPYING_FROM_CONTAINER,
+ proxy.getLink(),
+ container.name()),
+ e.getMessage()));
+ // for now
+ }
+ }
+ } catch (InterruptedException e) {
+ // do nothing
+ } catch (TarException | IOException e) {
+ Activator.log(e);
+ } finally {
+ monitor.done();
+ }
+ return Status.OK_STATUS;
+ }
+
+ };
+
+ copyFromContainerJob.schedule();
+
+ }
+
+}
diff --git a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/jobs/JobMessages.properties b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/jobs/JobMessages.properties
index 0aee2c1..1d8e660 100644
--- a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/jobs/JobMessages.properties
+++ b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/jobs/JobMessages.properties
@@ -20,3 +20,4 @@
DockerComposeStop.title=Stopping Docker Compose...
DockerComposeStop.error=Docker Compose failed to stop
RetrieveImageHierarchyJob.error=The selected element is not an Image nor a Container
+PopulateContainerFiles.msg=Populating container files...
diff --git a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/wizards/ContainerCopyFrom.java b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/wizards/ContainerCopyFrom.java
new file mode 100644
index 0000000..b52d1d1
--- /dev/null
+++ b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/wizards/ContainerCopyFrom.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Red Hat.
+ * 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:
+ * Red Hat - Initial Contribution
+ *******************************************************************************/
+package org.eclipse.linuxtools.internal.docker.ui.wizards;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.jface.dialogs.ProgressMonitorDialog;
+import org.eclipse.jface.wizard.Wizard;
+import org.eclipse.linuxtools.docker.core.IDockerConnection;
+import org.eclipse.linuxtools.docker.core.IDockerContainer;
+import org.eclipse.linuxtools.docker.ui.Activator;
+import org.eclipse.linuxtools.internal.docker.core.ContainerFileProxy;
+import org.eclipse.linuxtools.internal.docker.core.DockerConnection;
+import org.eclipse.linuxtools.internal.docker.ui.ContainerFileSystemProvider;
+import org.eclipse.linuxtools.internal.docker.ui.MinimizedFileSystemElement;
+import org.eclipse.linuxtools.internal.docker.ui.PopulateContainerFilesOperation;
+
+public class ContainerCopyFrom extends Wizard {
+
+ private DockerConnection connection;
+ private IDockerContainer container;
+ private String target;
+ private List<ContainerFileProxy> sources;
+ private ContainerCopyFromPage mainPage;
+
+ public ContainerCopyFrom(IDockerConnection connection,
+ IDockerContainer container) {
+ this.connection = (DockerConnection) connection;
+ this.container = container;
+ }
+
+ public String getTarget() {
+ return target;
+ }
+
+ public List<ContainerFileProxy> getSources() {
+ return sources;
+ }
+
+ @Override
+ public void addPages() {
+ ProgressMonitorDialog pd = new ProgressMonitorDialog(
+ Activator.getActiveWorkbenchShell());
+ ContainerFileSystemProvider provider = new ContainerFileSystemProvider(
+ connection, container.id());
+ PopulateContainerFilesOperation sfo = new PopulateContainerFilesOperation(
+ new ContainerFileProxy("", "", true), //$NON-NLS-1$ //$NON-NLS-2$
+ null, provider);
+ try {
+ pd.run(true, true, sfo);
+ } catch (InvocationTargetException | InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ mainPage = new ContainerCopyFromPage(sfo.getResult(), provider,
+ container.name());
+ addPage(mainPage);
+ }
+
+ @Override
+ public boolean canFinish() {
+ return mainPage.isPageComplete();
+ }
+
+ @Override
+ public boolean performFinish() {
+ target = mainPage.getTarget();
+ @SuppressWarnings("rawtypes")
+ Iterator iterator = mainPage.getValueIterator();
+ ArrayList<ContainerFileProxy> copyList = new ArrayList<>();
+ while (iterator.hasNext()) {
+ MinimizedFileSystemElement e = (MinimizedFileSystemElement) iterator
+ .next();
+ ContainerFileProxy p = (ContainerFileProxy) e.getFileSystemObject();
+ copyList.add(p);
+ }
+ sources = copyList;
+ return true;
+ }
+}
diff --git a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/wizards/ContainerCopyFromPage.java b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/wizards/ContainerCopyFromPage.java
new file mode 100644
index 0000000..27b4a64
--- /dev/null
+++ b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/wizards/ContainerCopyFromPage.java
@@ -0,0 +1,292 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Red Hat.
+ * 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:
+ * Red Hat - Initial Contribution
+ *******************************************************************************/
+package org.eclipse.linuxtools.internal.docker.ui.wizards;
+
+import java.io.File;
+import java.util.Iterator;
+
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.jface.layout.GridLayoutFactory;
+import org.eclipse.jface.viewers.ICheckStateListener;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.linuxtools.docker.ui.Activator;
+import org.eclipse.linuxtools.internal.docker.ui.CheckboxTreeAndListGroup;
+import org.eclipse.linuxtools.internal.docker.ui.MinimizedFileSystemElement;
+import org.eclipse.linuxtools.internal.docker.ui.SWTImagesFactory;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.DirectoryDialog;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.dialogs.FileSystemElement;
+import org.eclipse.ui.model.WorkbenchContentProvider;
+import org.eclipse.ui.model.WorkbenchLabelProvider;
+import org.eclipse.ui.model.WorkbenchViewerComparator;
+import org.eclipse.ui.wizards.datatransfer.IImportStructureProvider;
+
+/**
+ * A standard file selection dialog which solicits a list of files from the user.
+ * The <code>getResult</code> method returns the selected files.
+ * <p>
+ * This class may be instantiated; it is not intended to be subclassed.
+ * </p>
+ * <p>
+ * Example:
+ * <pre>
+ * FileSelectionDialog dialog =
+ * new FileSelectionDialog(getShell(), rootElement, msg);
+ * dialog.setInitialSelections(selectedResources);
+ * dialog.open();
+ * return dialog.getResult();
+ * </pre>
+ * </p>
+ * @noextend This class is not intended to be subclassed by clients.
+ */
+public class ContainerCopyFromPage extends WizardPage {
+
+ private final static String NAME = "ContainerCopyFrom.name"; //$NON-NLS-1$
+ private final static String TITLE = "ContainerCopyFrom.title"; //$NON-NLS-1$
+ private final static String DESC = "ContainerCopyFrom.desc"; //$NON-NLS-1$
+ private static final String TARGET_LABEL = "ContainerCopyFrom.target.label"; //$NON-NLS-1$
+ private static final String TARGET_TOOLTIP = "ContainerCopyFrom.target.tooltip"; //$NON-NLS-1$
+ private static final String NO_TARGET_SPECIFIED = "ContainerCopyFrom.notarget.error"; //$NON-NLS-1$
+ private static final String BROWSE_LABEL = "ContainerCopyFrom.browse.label"; //$NON-NLS-1$
+
+ // the root file representative to populate the viewer with
+ private FileSystemElement root;
+
+ private IImportStructureProvider structureProvider;
+
+ // the visual selection widget group
+ CheckboxTreeAndListGroup selectionGroup;
+
+ private Text targetText;
+ private Button browseButton;
+
+ private String target;
+
+ // sizing constants
+ private static final int SIZING_SELECTION_WIDGET_WIDTH = 500;
+
+ private static final int SIZING_SELECTION_WIDGET_HEIGHT = 250;
+ private static final int COLUMNS = 3;
+
+ /**
+ * Wizard page for copying files from container
+ *
+ * @param fileSystemElement
+ * - FileSystemElement of root
+ * @param structureProvider
+ * - IImportStructureProvider to get file system structure from
+ * container
+ * @param containerName
+ * - name of container
+ */
+ public ContainerCopyFromPage(FileSystemElement fileSystemElement,
+ IImportStructureProvider structureProvider, String containerName) {
+ super(WizardMessages.getString(NAME));
+ setDescription(WizardMessages.getFormattedString(DESC,
+ containerName));
+ setTitle(WizardMessages.getString(TITLE));
+ setImageDescriptor(SWTImagesFactory.DESC_WIZARD);
+ root = fileSystemElement;
+ this.structureProvider = structureProvider;
+ }
+
+
+ /*
+ * Return the host directory for the copy operation
+ */
+ public String getTarget() {
+ return target;
+ }
+
+ /*
+ * Get an iterator for the selected items to copy
+ */
+ @SuppressWarnings("rawtypes")
+ public Iterator getValueIterator() {
+ // TODO: look at also returning all checked table items and
+ // remove files from that directory to shorten the
+ // time taken to copy (i.e. copy the whole directory
+ // at once).
+ return selectionGroup.getAllCheckedListItems();
+ }
+
+ private ModifyListener Listener = e -> validate();
+ private ICheckStateListener CheckListener = e -> validate();
+
+ private void validate() {
+ boolean complete = true;
+ boolean error = false;
+
+ if (targetText.getText().length() == 0) {
+ error = true;
+ setErrorMessage(WizardMessages.getString(NO_TARGET_SPECIFIED));
+ } else {
+ File f = new File(targetText.getText());
+ if (!f.exists()) {
+ error = true;
+ setErrorMessage(WizardMessages.getString(NO_TARGET_SPECIFIED));
+ }
+ }
+
+ if (selectionGroup.getCheckedElementCount() == 0)
+ complete = false;
+
+ if (!error) {
+ target = targetText.getText();
+ setErrorMessage(null);
+ }
+ setPageComplete(complete && !error);
+ }
+
+ @Override
+ public void createControl(Composite parent) {
+ // page group
+ Composite composite = new Composite(parent, SWT.NULL);
+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).span(1, 1)
+ .grab(true, false).applyTo(composite);
+ GridLayoutFactory.fillDefaults().numColumns(COLUMNS).margins(6, 6)
+ .applyTo(composite);
+
+ Label targetLabel = new Label(composite, SWT.NULL);
+ targetLabel.setText(WizardMessages.getString(TARGET_LABEL));
+
+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).span(1, 1)
+ .grab(false, false).applyTo(targetLabel);
+
+ targetText = new Text(composite, SWT.BORDER | SWT.SINGLE);
+ targetText.addModifyListener(Listener);
+ targetText.setToolTipText(WizardMessages.getString(TARGET_TOOLTIP));
+
+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).span(1, 1)
+ .grab(true, false).applyTo(targetText);
+
+ browseButton = new Button(composite, SWT.NONE);
+ browseButton.setText(WizardMessages.getString(BROWSE_LABEL));
+ browseButton.addSelectionListener(onBrowseSelect());
+
+ GridDataFactory.fillDefaults().align(SWT.END, SWT.FILL).span(1, 1)
+ .grab(false, false).applyTo(browseButton);
+
+ // Create a fake parent of the root to be the dialog input element.
+ // Use an empty label so that display of the element's full name
+ // doesn't include a confusing label
+ FileSystemElement input = new FileSystemElement("", null, true);//$NON-NLS-1$
+ input.addChild(root);
+ root.setParent(input);
+
+ Composite selectionComposite = new Composite(composite, SWT.NULL);
+ GridLayout selectionLayout = new GridLayout();
+ selectionComposite.setLayout(selectionLayout);
+
+ selectionGroup = new CheckboxTreeAndListGroup(selectionComposite, input,
+ getFolderProvider(), getDynamicFolderProvider(),
+ new WorkbenchLabelProvider(),
+ getFileProvider(), new WorkbenchLabelProvider(), SWT.NONE,
+ SIZING_SELECTION_WIDGET_WIDTH, // since this page has no other significantly-sized
+ SIZING_SELECTION_WIDGET_HEIGHT); // widgets we need to hardcode the combined widget's
+ // size, otherwise it will open too small
+
+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).span(3, 1)
+ .grab(true, false).applyTo(selectionComposite);
+
+ WorkbenchViewerComparator comparator = new WorkbenchViewerComparator();
+ selectionGroup.setTreeComparator(comparator);
+ selectionGroup.setListComparator(comparator);
+ selectionGroup.addCheckStateListener(CheckListener);
+
+ setControl(composite);
+ validate();
+ setPageComplete(false);
+
+ selectionGroup.aboutToOpen();
+ }
+
+ private SelectionListener onBrowseSelect() {
+ final ContainerCopyFromPage page = this;
+ return new SelectionAdapter() {
+ @Override
+ public void widgetSelected(final SelectionEvent e) {
+ DirectoryDialog d = new DirectoryDialog(
+ Activator.getActiveWorkbenchShell());
+ String x = d.open();
+ if (x != null) {
+ page.targetText.setText(x);
+ }
+ }
+ };
+
+ }
+ /**
+ * Returns a content provider for <code>FileSystemElement</code>s that returns
+ * only files as children.
+ */
+ private ITreeContentProvider getFileProvider() {
+ return new WorkbenchContentProvider() {
+ @Override
+ public Object[] getChildren(Object o) {
+ if (o instanceof FileSystemElement) {
+ return ((FileSystemElement) o).getFiles().getChildren(o);
+ }
+
+ return new Object[0];
+ }
+ };
+ }
+
+ /**
+ * Returns a content provider for <code>FileSystemElement</code>s that returns
+ * only folders as children.
+ */
+ private ITreeContentProvider getFolderProvider() {
+ return new WorkbenchContentProvider() {
+ @Override
+ public Object[] getChildren(Object o) {
+ if (o instanceof FileSystemElement) {
+ return ((FileSystemElement) o).getFolders().getChildren(o);
+ }
+
+ return new Object[0];
+ }
+ };
+ }
+
+ /**
+ * Returns a content provider for <code>FileSystemElement</code>s that
+ * returns only folders as children.
+ */
+ private ITreeContentProvider getDynamicFolderProvider() {
+ return new WorkbenchContentProvider() {
+ @Override
+ public Object[] getChildren(Object o) {
+ if (o instanceof MinimizedFileSystemElement) {
+ return ((MinimizedFileSystemElement) o)
+ .getFolders(structureProvider)
+ .getChildren(o);
+ } else if (o instanceof FileSystemElement) {
+ return ((FileSystemElement) o).getFolders().getChildren(o);
+ }
+
+ return new Object[0];
+ }
+ };
+ }
+
+}
diff --git a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/wizards/WizardMessages.properties b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/wizards/WizardMessages.properties
index 45f0b51..eda9108 100644
--- a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/wizards/WizardMessages.properties
+++ b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/wizards/WizardMessages.properties
@@ -164,6 +164,14 @@
ContainerCommit.title=Commit Container
ContainerCommit.name=Commit Container
+ContainerCopyFrom.desc=Copy files from container <{0}> to host directory
+ContainerCopyFrom.title=Copy From Container
+ContainerCopyFrom.name=Copy From Container
+ContainerCopyFrom.target.label=Host Directory
+ContainerCopyFrom.target.tooltip=Specify a directory on host to copy files to
+ContainerCopyFrom.notarget.error=A valid host directory must be specified
+ContainerCopyFrom.browse.label=Browse...
+
ImageName.toolTip=Enter name for image (either REPOSITORY or REPOSITORY:TAG)
ErrorInvalidRepo.msg=Image name is invalid