blob: 7c85c2e2f348fc1724546dd2c8b666f2e9952f55 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016, 2020 Red Hat Inc. and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat - Initial Contribution
*******************************************************************************/
package org.eclipse.linuxtools.internal.docker.ui.commands;
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFilePermission;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
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.Platform;
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.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;
}
/**
* A blocking input stream that waits until data is available.
*/
private class BlockingInputStream extends InputStream {
private InputStream in;
public BlockingInputStream(InputStream in) {
this.in = in;
}
@Override
public int read() throws IOException {
return in.read();
}
}
private static Set<PosixFilePermission> toPerms(int mode) {
Set<PosixFilePermission> perms = new HashSet<>();
if ((mode & 0400) != 0) {
perms.add(PosixFilePermission.OWNER_READ);
}
if ((mode & 0200) != 0) {
perms.add(PosixFilePermission.OWNER_WRITE);
}
if ((mode & 0100) != 0) {
perms.add(PosixFilePermission.OWNER_EXECUTE);
}
if ((mode & 0040) != 0) {
perms.add(PosixFilePermission.GROUP_READ);
}
if ((mode & 0020) != 0) {
perms.add(PosixFilePermission.GROUP_WRITE);
}
if ((mode & 0010) != 0) {
perms.add(PosixFilePermission.GROUP_EXECUTE);
}
if ((mode & 0004) != 0) {
perms.add(PosixFilePermission.OTHERS_READ);
}
if ((mode & 0002) != 0) {
perms.add(PosixFilePermission.OTHERS_WRITE);
}
if ((mode & 0001) != 0) {
perms.add(PosixFilePermission.OTHERS_EXECUTE);
}
return perms;
}
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());
boolean isWin = Platform.getOS().equals(Platform.OS_WIN32);
try (Closeable token = ((DockerConnection) connection).getOperationToken()) {
Map<String, String> links = new HashMap<>();
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);
InputStream in = ((DockerConnection) connection).copyContainer(token, container.id(),
proxy.getLink());
/*
* The input stream from copyContainer might be incomplete or non-blocking so we
* should wrap it in a stream that is guaranteed to block until data is
* available.
*/
try (TarArchiveInputStream k = new TarArchiveInputStream(new BlockingInputStream(in))) {
TarArchiveEntry te = null;
while ((te = k.getNextTarEntry()) != null) {
long size = te.getSize();
IPath path = new Path(target);
path = path.append(te.getName());
File f = new File(path.toOSString());
int mode = te.getMode();
if (te.isDirectory()) {
f.mkdir();
if (!isWin && !te.isSymbolicLink()) {
Files.setPosixFilePermissions(Paths.get(path.toOSString()), toPerms(mode));
}
continue;
} else {
f.createNewFile();
if (!isWin && !te.isSymbolicLink()) {
Files.setPosixFilePermissions(Paths.get(path.toOSString()), toPerms(mode));
}
}
try (FileOutputStream os = new FileOutputStream(f)) {
int bufferSize = ((int) size > 4096 ? 4096 : (int) size);
byte[] barray = new byte[bufferSize];
int result = -1;
if (size == 0 && te.isSymbolicLink()) {
IPath linkPath = new Path(te.getLinkName());
if (!linkPath.isAbsolute()) {
if (proxy.isFolder()) {
linkPath = new Path(proxy.getFullPath()).append(te.getLinkName());
} else {
linkPath = new Path(proxy.getFullPath()).removeLastSegments(1)
.append(te.getLinkName());
}
}
links.put(te.getName(), linkPath.toPortableString());
} else {
while ((result = k.read(barray, 0, bufferSize)) > -1) {
if (monitor.isCanceled()) {
monitor.done();
k.close();
os.close();
return Status.CANCEL_STATUS;
}
os.write(barray, 0, result);
}
}
}
}
}
} catch (final DockerException e) {
Display.getDefault()
.syncExec(() -> MessageDialog.openError(
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
CommandMessages.getFormattedString(ERROR_COPYING_FROM_CONTAINER,
proxy.getLink(), container.name()),
e.getCause() != null ? e.getCause().getMessage() : e.getMessage()));
}
}
for (String name : links.keySet()) {
String link = links.get(name);
try {
InputStream in = ((DockerConnection) connection).copyContainer(token, container.id(),
link);
/*
* The input stream from copyContainer might be incomplete or non-blocking so we
* should wrap it in a stream that is guaranteed to block until data is
* available.
*/
try (TarArchiveInputStream k = new TarArchiveInputStream(new BlockingInputStream(in))) {
TarArchiveEntry te = k.getNextTarEntry();
if (te != null) {
long size = te.getSize();
IPath path = new Path(target);
path = path.append(name);
File f = new File(path.toOSString());
f.createNewFile();
try (FileOutputStream os = new FileOutputStream(f)) {
int bufferSize = ((int) size > 4096 ? 4096 : (int) size);
byte[] barray = new byte[bufferSize];
int result = -1;
while ((result = k.read(barray, 0, bufferSize)) > -1) {
if (monitor.isCanceled()) {
monitor.done();
k.close();
os.close();
return Status.CANCEL_STATUS;
}
os.write(barray, 0, result);
}
}
}
}
} catch (final DockerException e) {
Display.getDefault()
.syncExec(() -> MessageDialog.openError(
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
CommandMessages.getFormattedString(ERROR_COPYING_FROM_CONTAINER,
name, container.name()),
e.getCause() != null ? e.getCause().getMessage() : e.getMessage()));
}
}
} catch (InterruptedException e) {
// do nothing
} catch (IOException e) {
Activator.log(e);
} catch (DockerException e1) {
Activator.log(e1);
} finally {
monitor.done();
}
return Status.OK_STATUS;
}
};
copyFromContainerJob.schedule();
}
}