/*******************************************************************************
* Copyright (c) 2014, 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.views;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.linuxtools.docker.core.DockerConnectionManager;
import org.eclipse.linuxtools.docker.core.DockerException;
import org.eclipse.linuxtools.docker.core.EnumDockerConnectionState;
import org.eclipse.linuxtools.docker.core.IDockerConnection;
import org.eclipse.linuxtools.docker.core.IDockerContainer;
import org.eclipse.linuxtools.docker.core.IDockerContainerInfo;
import org.eclipse.linuxtools.docker.core.IDockerHostConfig;
import org.eclipse.linuxtools.docker.core.IDockerImage;
import org.eclipse.linuxtools.docker.core.IDockerNetworkSettings;
import org.eclipse.linuxtools.docker.core.IDockerPortBinding;
import org.eclipse.linuxtools.docker.core.IDockerPortMapping;
import org.eclipse.linuxtools.docker.ui.Activator;
import org.eclipse.linuxtools.internal.docker.core.DockerContainer;
import org.eclipse.linuxtools.internal.docker.core.DockerImage;
import org.eclipse.linuxtools.internal.docker.core.DockerPortMapping;
import org.eclipse.swt.widgets.Display;
/**
* {@link ITreeContentProvider} for the {@link DockerExplorerView}
*
*/
public class DockerExplorerContentProvider implements ITreeContentProvider {
private final Object[] EMPTY = new Object[0];
private Map<IDockerConnection, Job> openRetryJobs = new HashMap<>();
private TreeViewer viewer;
@Override
public void dispose() {
for (Job job : openRetryJobs.values()) {
LoadingJob loadingJob = (LoadingJob) job;
IProgressMonitor monitor = loadingJob.getMonitor();
monitor.setCanceled(true);
job.cancel();
try {
job.join();
} catch (InterruptedException e) {
// ignore
}
}
}
@Override
public void inputChanged(final Viewer viewer, final Object oldInput,
final Object newInput) {
this.viewer = (TreeViewer) viewer;
}
@Override
public Object[] getElements(final Object inputElement) {
if (inputElement instanceof DockerConnectionManager) {
final DockerConnectionManager connectionManager = (DockerConnectionManager) inputElement;
return connectionManager.getConnections();
}
return EMPTY;
}
@Override
public Object[] getChildren(final Object parentElement) {
if (parentElement instanceof IDockerConnection) {
// check the connection availability before returning the
// 'containers' and 'images' child nodes.
final IDockerConnection connection = (IDockerConnection) parentElement;
if (connection.isOpen()) {
return new Object[] { new DockerImagesCategory(connection),
new DockerContainersCategory(connection) };
} else if (connection
.getState() == EnumDockerConnectionState.UNKNOWN) {
open(connection);
return new Object[] { new LoadingStub(connection) };
} else if (connection
.getState() == EnumDockerConnectionState.CLOSED) {
synchronized (openRetryJobs) {
Job job = openRetryJobs.get(connection);
if (job == null) {
openRetry(connection);
}
}
return new Object[] { new LoadingStub(connection) };
}
return new Object[0];
} else if (parentElement instanceof DockerContainersCategory) {
final DockerContainersCategory containersCategory = (DockerContainersCategory) parentElement;
final IDockerConnection connection = containersCategory.getConnection();
if(connection.isContainersLoaded()) {
return connection.getContainers().toArray();
}
loadContainers(containersCategory);
return new Object[] { new LoadingStub(containersCategory) };
} else if (parentElement instanceof DockerImagesCategory) {
final DockerImagesCategory imagesCategory = (DockerImagesCategory) parentElement;
final IDockerConnection connection = imagesCategory.getConnection();
if(connection.isImagesLoaded()) {
// here we duplicate the images to show one org/repo with all
// its tags per node in the tree
final List<IDockerImage> allImages = connection.getImages();
final List<IDockerImage> explorerImages = splitImageTagsByRepo(
allImages);
return explorerImages.toArray();
}
loadImages(imagesCategory);
return new Object[] { new LoadingStub(imagesCategory) };
} else if (parentElement instanceof IDockerContainer) {
final DockerContainer container = (DockerContainer) parentElement;
if (container.isInfoLoaded()) {
final IDockerContainerInfo info = container.info();
final IDockerNetworkSettings networkSettings = (info != null)
? info.networkSettings() : null;
final IDockerHostConfig hostConfig = (info != null)
? info.hostConfig() : null;
return new Object[] {
new DockerContainerPortMappingsCategory(container,
(networkSettings != null)
? networkSettings.ports()
: Collections
.<String, List<IDockerPortBinding>> emptyMap()),
new DockerContainerVolumesCategory(container,
(hostConfig != null) ? hostConfig.binds()
: Collections.<String> emptyList()),
new DockerContainerLinksCategory(container,
(hostConfig != null) ? hostConfig.links()
: Collections.<String> emptyList()) };
}
loadContainerInfo(container);
return new Object[] { new LoadingStub(container) };
} else if (parentElement instanceof DockerContainerLinksCategory) {
final DockerContainerLinksCategory linksCategory = (DockerContainerLinksCategory) parentElement;
return linksCategory.getLinks().toArray();
} else
if (parentElement instanceof DockerContainerPortMappingsCategory) {
final DockerContainerPortMappingsCategory portMappingsCategory = (DockerContainerPortMappingsCategory) parentElement;
return portMappingsCategory.getPortMappings().toArray();
} else if (parentElement instanceof DockerContainerVolumesCategory) {
final DockerContainerVolumesCategory volumesCategory = (DockerContainerVolumesCategory) parentElement;
return volumesCategory.getVolumes().toArray();
}
return EMPTY;
}
/**
* Iterates on the given {@code images} and duplicates the elements that
* have multiple repositories
*
* @param images
* the {@link List} of {@link IDockerImage} to process
* @return the resulting {@link List} containing duplicate
* {@link IDockerImage} when the source had multiple repositories.
*/
public static List<IDockerImage> splitImageTagsByRepo(
final List<IDockerImage> images) {
return images.stream()
.flatMap(image -> DockerImage.duplicateImageByRepo(image))
.collect(Collectors.toList());
}
/**
* Call the {@link IDockerConnection#getContainers(boolean)} in a background
* job to avoid blocking the UI.
*
* @param connection
* the connection to ping
*/
private void open(final IDockerConnection connection) {
final Job pingJob = new LoadingJob(DVMessages.getString("PingJob.msg"), //$NON-NLS-1$
connection) {
@Override
protected IStatus run(final IProgressMonitor monitor) {
try {
connection.open(true);
connection.ping();
return Status.OK_STATUS;
} catch (DockerException e) {
Activator.logWarningMessage(DVMessages.getFormattedString(
"PingJobError.msg.withExplanation", //$NON-NLS-1$
connection.getName(), e.getMessage()));
return Status.CANCEL_STATUS;
}
}
};
pingJob.schedule();
}
/**
* Call the {@link IDockerConnection#open(boolean)} in a background job to
* continually retry opening the connection and avoid blocking the UI.
*
* @param connection
* the connection to open/ping
*/
private void openRetry(final IDockerConnection connection) {
final Job openRetryJob = new LoadingJob(
DVMessages.getFormattedString("PingJob2.msg", //$NON-NLS-1$
connection.getName(), connection.getUri()),
connection) {
@Override
protected IStatus run(final IProgressMonitor monitor) {
setMonitor(monitor);
long totalSleep = 0;
long sleepTime = 3000; // 3 second default
for (;;) {
try {
// check if Connection is removed or cancelled
if (monitor.isCanceled() || DockerConnectionManager
.getInstance()
.getConnectionByUri(
connection.getUri()) == null) {
synchronized (openRetryJobs) {
openRetryJobs.remove(connection);
}
return Status.CANCEL_STATUS;
}
connection.open(true);
connection.ping();
synchronized (openRetryJobs) {
openRetryJobs.remove(connection);
}
return Status.OK_STATUS;
} catch (DockerException e) {
// ignore
}
try {
Thread.sleep(sleepTime);
totalSleep += sleepTime;
// if we have tried for over 5 minutes, switch to the
// container refresh rate which defaults to 15 seconds.
// This should slow down the interference of connections
// we never use.
if (totalSleep > 300000) {
totalSleep = 0; // prevent a future overflow
sleepTime = Platform.getPreferencesService()
.getLong("org.eclipse.linuxtools.docker.ui", //$NON-NLS-1$
"containerRefreshTime", 15000, //$NON-NLS-1$
null);
}
} catch (InterruptedException e) {
synchronized (openRetryJobs) {
openRetryJobs.remove(connection);
}
return Status.CANCEL_STATUS;
}
}
}
};
synchronized (openRetryJobs) {
openRetryJobs.put(connection, openRetryJob);
}
openRetryJob.setSystem(true);
openRetryJob.schedule();
}
/**
* Call the {@link IDockerConnection#getContainers(boolean)} in a background
* job to avoid blocking the UI.
*
* @param containersCategory
* the selected {@link DockerContainersCategory}
*/
private void loadContainers(
final DockerContainersCategory containersCategory) {
final Job loadContainersJob = new LoadingJob(
DVMessages.getString("ContainersLoadJob.msg"), //$NON-NLS-1$
containersCategory) {
@Override
protected IStatus run(final IProgressMonitor monitor) {
containersCategory.getConnection().getContainers(true);
return Status.OK_STATUS;
}
};
loadContainersJob.schedule();
}
/**
* Call the {@link IDockerConnection#getContainers(boolean)} in a background
* job to avoid blocking the UI.
*
* @param container
* the selected {@link DockerContainersCategory}
*/
private void loadContainerInfo(final IDockerContainer container) {
final Job loadContainersJob = new LoadingJob(
DVMessages.getString("ContainerInfoLoadJob.msg"), container) { //$NON-NLS-1$
@Override
protected IStatus run(final IProgressMonitor monitor) {
((DockerContainer) container).info(true);
return Status.OK_STATUS;
}
};
loadContainersJob.schedule();
}
/**
* Call the {@link IDockerConnection#getImages(boolean)} in a background job
* to avoid blocking the UI.
*
* @param imagesCategory
* the selected {@link DockerImagesCategory}
*/
private void loadImages(final DockerImagesCategory imagesCategory) {
final Job loadImagesJob = new LoadingJob(
DVMessages.getString("ImagesLoadJob.msg"), imagesCategory) { //$NON-NLS-1$
@Override
protected IStatus run(final IProgressMonitor monitor) {
imagesCategory.getConnection().getImages(true);
return Status.OK_STATUS;
}
};
loadImagesJob.schedule();
}
@Override
public Object getParent(final Object element) {
if (element instanceof DockerImagesCategory) {
return ((DockerImagesCategory) element).getConnection();
} else if (element instanceof DockerContainersCategory) {
return ((DockerContainersCategory) element).getConnection();
}
return null;
}
@Override
public boolean hasChildren(final Object element) {
// We want to automate enabling a connection.
// If the connection is closed (meaning we tried to open
// and failed), then kick off a retry job.
// Don't start a retry job if one is already running.
if (element instanceof IDockerConnection) {
IDockerConnection connection = (IDockerConnection) element;
if (connection
.getState() != EnumDockerConnectionState.ESTABLISHED) {
Job openRetryJob = null;
synchronized (openRetryJobs) {
openRetryJob = openRetryJobs.get(connection);
}
if (openRetryJob == null) {
openRetry(connection);
}
}
}
return (element instanceof IDockerConnection
|| element instanceof DockerContainersCategory
|| element instanceof DockerImagesCategory
|| element instanceof IDockerContainer
|| (element instanceof DockerContainerLinksCategory
&& !((DockerContainerLinksCategory) element).getLinks()
.isEmpty())
|| (element instanceof DockerContainerPortMappingsCategory
&& !((DockerContainerPortMappingsCategory) element)
.getPortMappings().isEmpty())
|| (element instanceof DockerContainerVolumesCategory
&& !((DockerContainerVolumesCategory) element)
.getVolumes().isEmpty()));
}
/**
* Wrapper node to display {@link IDockerImage} of a given
* {@link IDockerConnection}
*/
public static class DockerImagesCategory implements IAdaptable {
private final IDockerConnection connection;
/**
* @param container
* - Docker container
*/
public DockerImagesCategory(final IDockerConnection connection) {
this.connection = connection;
}
@SuppressWarnings("unchecked")
@Override
public <T> T getAdapter(final Class<T> adapter) {
if (adapter.equals(IDockerConnection.class)) {
return (T) getConnection();
}
return null;
}
public IDockerConnection getConnection() {
return connection;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((connection == null) ? 0 : connection.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DockerImagesCategory other = (DockerImagesCategory) obj;
if (connection == null) {
if (other.connection != null)
return false;
} else if (!connection.equals(other.connection))
return false;
return true;
}
}
/**
* Wrapper node to display {@link IDockerContainer} of a given
* {@link IDockerConnection}
*/
public static class DockerContainersCategory implements IAdaptable {
private final IDockerConnection connection;
/**
* @param container
* - Docker container
*/
public DockerContainersCategory(final IDockerConnection connection) {
this.connection = connection;
}
@SuppressWarnings("unchecked")
@Override
public <T> T getAdapter(final Class<T> adapter) {
if (adapter.equals(IDockerConnection.class)) {
return (T) getConnection();
}
return null;
}
public IDockerConnection getConnection() {
return connection;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((connection == null) ? 0 : connection.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DockerContainersCategory other = (DockerContainersCategory) obj;
if (connection == null) {
if (other.connection != null)
return false;
} else if (!connection.equals(other.connection))
return false;
return true;
}
}
/**
* Wrapper node to display {@link IDockerPortMapping} of a given
* {@link IDockerContainer}
*/
public static class DockerContainerPortMappingsCategory
implements IAdaptable {
private final IDockerContainer container;
private final List<IDockerPortMapping> portMappings;
/**
* @param container
* @param bindings
* - the container bindings
*/
public DockerContainerPortMappingsCategory(
final IDockerContainer container,
final Map<String, List<IDockerPortBinding>> bindings) {
this.container = container;
this.portMappings = new ArrayList<>();
if (bindings != null) {
for (Entry<String, List<IDockerPortBinding>> entry : bindings
.entrySet()) {
// internal port is in the following form: "8080/tcp"
final String[] source = entry.getKey().split("/");
final int privatePort = Integer.parseInt(source[0]);
final String type = source[1];
for (IDockerPortBinding portBinding : entry.getValue()) {
portMappings.add(
new DockerPortMapping(container, privatePort,
Integer.parseInt(
portBinding.hostPort()),
type, portBinding.hostIp()));
}
}
}
Collections.sort(portMappings,
(portMapping,
otherPortMapping) -> portMapping.getPrivatePort()
- otherPortMapping.getPrivatePort());
}
@SuppressWarnings("unchecked")
@Override
public <T> T getAdapter(final Class<T> adapter) {
if (adapter.equals(IDockerConnection.class)) {
return (T) getContainer().getConnection();
}
return null;
}
public IDockerContainer getContainer() {
return container;
}
public List<IDockerPortMapping> getPortMappings() {
return this.portMappings;
}
@Override
public String toString() {
return "Port mappings for " + this.container.name();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((container == null) ? 0 : container.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DockerContainerPortMappingsCategory other = (DockerContainerPortMappingsCategory) obj;
if (container == null) {
if (other.container != null)
return false;
} else if (!container.equals(other.container))
return false;
return true;
}
}
/**
* Wrapper node to display the {@link DockerContainerLink} of a given
* {@link IDockerContainer}
*/
public static class DockerContainerLinksCategory implements IAdaptable {
private final IDockerContainer container;
private final List<DockerContainerLink> links;
/**
* Constructor.
*
* @param container
*
* @param links
* - the container links
*/
public DockerContainerLinksCategory(final IDockerContainer container,
final List<String> links) {
this.container = container;
this.links = new ArrayList<>();
if (links != null) {
for (String link : links) {
this.links.add(new DockerContainerLink(container, link));
}
}
}
@SuppressWarnings("unchecked")
@Override
public <T> T getAdapter(final Class<T> adapter) {
if (adapter.equals(IDockerConnection.class)) {
return (T) getContainer().getConnection();
}
return null;
}
public IDockerContainer getContainer() {
return container;
}
public List<DockerContainerLink> getLinks() {
if (this.links == null) {
return Collections.emptyList();
}
return this.links;
}
@Override
public String toString() {
return "Container links for " + this.container.name();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((container == null) ? 0 : container.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DockerContainerLinksCategory other = (DockerContainerLinksCategory) obj;
if (container == null) {
if (other.container != null)
return false;
} else if (!container.equals(other.container))
return false;
return true;
}
}
public static class DockerContainerLink implements IAdaptable {
private final IDockerContainer container;
private final String containerName;
private final String containerAlias;
/**
* Constructor.
*
* @param linkValue
* the bind value provided by the {@link IDockerHostConfig}.
*/
public DockerContainerLink(final IDockerContainer container,
final String linkValue) {
this.container = container;
// format: "container_name:containerAlias"
final String[] args = linkValue.split(":");
this.containerName = getDisplayableContainerName(args[0]);
this.containerAlias = args.length > 0
? getDisplayableContainerAlias(args[1]) : null;
}
@SuppressWarnings("unchecked")
@Override
public <T> T getAdapter(final Class<T> adapter) {
if (adapter.equals(IDockerConnection.class)) {
return (T) getContainer().getConnection();
}
return null;
}
public IDockerContainer getContainer() {
return container;
}
/**
* Removes the heading "/" i(if found) in the given container name
*
* @param containerName
* @return a displayable container name
*/
private String getDisplayableContainerName(final String containerName) {
return containerName.startsWith("/") ? containerName.substring(1)
: containerName;
}
/**
* Removes the heading "/" i(if found) in the given container name
*
* @param containerName
* @return a displayable container name
*/
private String getDisplayableContainerAlias(
final String containerAlias) {
final String[] containerAliasSplit = containerAlias.split("/");
if (containerAliasSplit.length > 1) {
return containerAliasSplit[2];
}
return null;
}
public String getContainerName() {
return containerName;
}
public String getContainerAlias() {
return containerAlias;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((containerAlias == null) ? 0
: containerAlias.hashCode());
result = prime * result
+ ((containerName == null) ? 0 : containerName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DockerContainerLink other = (DockerContainerLink) obj;
if (containerAlias == null) {
if (other.containerAlias != null)
return false;
} else if (!containerAlias.equals(other.containerAlias))
return false;
if (containerName == null) {
if (other.containerName != null)
return false;
} else if (!containerName.equals(other.containerName))
return false;
return true;
}
}
/**
* Wrapper node to display {@link DockerContainerVolume} of a given
* {@link IDockerContainer}
*/
public static class DockerContainerVolumesCategory implements IAdaptable {
private final IDockerContainer container;
private final List<DockerContainerVolume> volumes;
/**
* Constructor.
*
* @param container
*
* @param volumes
* - the parent Docker container
*/
public DockerContainerVolumesCategory(final IDockerContainer container,
final List<String> volumes) {
this.container = container;
this.volumes = new ArrayList<>();
if (volumes != null) {
for (String volume : volumes) {
this.volumes
.add(new DockerContainerVolume(container, volume));
}
}
}
@SuppressWarnings("unchecked")
@Override
public <T> T getAdapter(final Class<T> adapter) {
if (adapter.equals(IDockerConnection.class)) {
return (T) getContainer().getConnection();
}
return null;
}
public IDockerContainer getContainer() {
return container;
}
public List<DockerContainerVolume> getVolumes() {
if (this.volumes == null) {
return Collections.emptyList();
}
return volumes;
}
@Override
public String toString() {
return "Volumes for " + this.container.name();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((container == null) ? 0 : container.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DockerContainerVolumesCategory other = (DockerContainerVolumesCategory) obj;
if (container == null) {
if (other.container != null)
return false;
} else if (!container.equals(other.container))
return false;
return true;
}
}
public static class DockerContainerVolume implements IAdaptable {
private final IDockerContainer container;
private final String hostPath;
private final String containerPath;
private final String flags;
/**
* @param container
* @param volume
* the volume value provided by the {@link IDockerHostConfig}
* .
* @return a {@link DockerContainerVolume}
*/
public DockerContainerVolume(final IDockerContainer container,
final String volume) {
this.container = container;
// (1) "container_path" to create a new volume for the container
// (2) "host_path:container_path" to bind-mount a host path into the
// container
// (3) "host_path:container_path:ro" to make the bind-mount
// read-only
// inside the container.
final String[] args = volume.split(":");
// on case (1), hostPath is null
this.hostPath = args.length > 1 ? args[0] : null;
// on case (1), containerPath is the first (and only) arg, otherwise
// it's the second one.
this.containerPath = args.length > 1 ? args[1] : args[0];
// flags exists on case (3) only
this.flags = args.length > 2 ? args[2] : null;
}
@SuppressWarnings("unchecked")
@Override
public <T> T getAdapter(final Class<T> adapter) {
if (adapter.equals(IDockerConnection.class)) {
return (T) getContainer().getConnection();
}
return null;
}
public IDockerContainer getContainer() {
return container;
}
public String getHostPath() {
return hostPath;
}
public String getContainerPath() {
return containerPath;
}
public String getFlags() {
return flags;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((containerPath == null) ? 0 : containerPath.hashCode());
result = prime * result + ((flags == null) ? 0 : flags.hashCode());
result = prime * result
+ ((hostPath == null) ? 0 : hostPath.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DockerContainerVolume other = (DockerContainerVolume) obj;
if (containerPath == null) {
if (other.containerPath != null)
return false;
} else if (!containerPath.equals(other.containerPath))
return false;
if (flags == null) {
if (other.flags != null)
return false;
} else if (!flags.equals(other.flags))
return false;
if (hostPath == null) {
if (other.hostPath != null)
return false;
} else if (!hostPath.equals(other.hostPath))
return false;
return true;
}
}
/**
* Node to indicate that a job is running and loading data.
*/
public static class LoadingStub {
private final Object element;
public LoadingStub(final Object element) {
this.element = element;
}
public Object getElement() {
return element;
}
}
private abstract class LoadingJob extends Job {
private IProgressMonitor monitor;
public LoadingJob(final String name, final Object target) {
super(name);
this.addJobChangeListener(new JobChangeAdapter() {
@Override
public void done(final IJobChangeEvent event) {
refreshTarget(target);
}
});
}
public IProgressMonitor getMonitor() {
return monitor;
}
public void setMonitor(IProgressMonitor monitor) {
this.monitor = monitor;
}
@Override
public boolean belongsTo(Object family) {
return DockerExplorerView.class.equals(family);
}
/**
* Refresh the whole content tree for the <strong>given target node and
* all its subelements</strong>.
*
* @param target
* the node to refresh
*/
private void refreshTarget(final Object target) {
// this piece of code must run in an async manner to avoid reentrant
// call while viewer is busy.
Display.getDefault().asyncExec(() -> {
if (viewer != null && !viewer.getControl().isDisposed()) {
final TreePath[] treePaths = viewer.getExpandedTreePaths();
viewer.refresh(target, true);
viewer.setExpandedTreePaths(treePaths);
}
});
}
}
}