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