/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.instancemanagement.internal; import java.io.File; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; import org.apache.commons.io.FileUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import de.rcenvironment.core.toolkitbridge.transitional.ConcurrencyUtils; import de.rcenvironment.core.utils.common.StringUtils; import de.rcenvironment.core.utils.common.textstream.TextOutputReceiver; import de.rcenvironment.core.utils.executor.LocalApacheCommandLineExecutor; import de.rcenvironment.core.utils.incubator.FileSystemOperations; import de.rcenvironment.core.utils.incubator.ZipFolderUtil; /** * I/O operations for managing external installations, like downloading, unpacking, deleting etc. * * @author Robert Mischke */ public class DeploymentOperationsImpl { private static final String RCE = "rce"; private static final int BYTE_TO_MEGABYTE_FACTOR = 1024 * 1024; private static final int CONSOLE_SHOW_PROGRESS_INTERVAL = 500; private final Log log = LogFactory.getLog(getClass()); private volatile boolean downloadRunning = false; private TextOutputReceiver userOutputReceiver; /** * Downloads a file from the given URL. If the download fails for any reason, an {@link IOException} is thrown. * * @param url the URL to download from * @param targetFile the file to write to * @param allowOverwrite whether the target file is allowed to already exist * @param showProgress whether the progress should be shown on console * @throws IOException on download failure, or if the target file already exists with "allowOverwrite" set to false */ public void downloadFile(final String url, final File targetFile, boolean allowOverwrite, boolean showProgress) throws IOException { // - MUST detect the case if the URL is invalid or does not exist (e.g., do not just return an empty file) // - SHOULD try to detect incomplete downloads (disk full etc), if possible String targetPath = targetFile.getAbsolutePath(); if (!allowOverwrite && targetFile.exists()) { throw new IOException("Target file " + targetPath + " does already exist"); } log.debug("Starting download of " + url + " to " + targetPath); final long sizeOfRequestedFile = getFileSize(new URL(url)); if (sizeOfRequestedFile > targetFile.getParentFile().getUsableSpace()) { throw new IOException("Download failed, not enough diskspace."); } // FIXME validate URL and file strings for security downloadRunning = true; // show download progress if (showProgress) { ConcurrencyUtils.getAsyncTaskService().execute(new Runnable() { @Override public void run() { while (downloadRunning && targetFile.length() < sizeOfRequestedFile) { userOutputReceiver.addOutput("Downloaded " + targetFile.length() / BYTE_TO_MEGABYTE_FACTOR + " MB of " + sizeOfRequestedFile / BYTE_TO_MEGABYTE_FACTOR + " MB"); try { Thread.sleep(CONSOLE_SHOW_PROGRESS_INTERVAL); } catch (InterruptedException e) { log.error("InterruptedException while download progress thread sleeps."); } } userOutputReceiver.addOutput("Download finished."); } }, "checkSize"); } try { FileUtils.copyURLToFile(new URL(url), targetFile); } finally { downloadRunning = false; } } /** * Extracts a product zip and prepares it for execution (e.g. required "chmod" calls). * * @param zipFile the product zip file * @param installationDir the installation directory; it must not already exist, and will directly contain the main executable on * success (ie no additional "rce/" folder inside this directory) * @throws IOException on setup failure; in this case, this method will try to remove the incomplete directory */ public void installFromProductZip(File zipFile, File installationDir) throws IOException { // - for safety, the installationDir *must not* already exist (to prevent any overwriting) // - try to delete the (incomplete) directory on any failure if (installationDir.exists()) { throw new IOException("Target installation directory does already exist: " + installationDir.getAbsolutePath()); } log.debug("Starting to extract " + zipFile.getAbsolutePath() + " to " + installationDir.getAbsolutePath()); ZipFolderUtil.extractZipToFolder(installationDir, zipFile); // move all content one folder level "up" File rceFolder = new File(installationDir, RCE); if (!rceFolder.isDirectory()) { throw new IOException("Expected 'rce' folder does not exist"); } File newFolder = new File(installationDir, "something"); rceFolder.renameTo(newFolder); for (File contentFile : newFolder.listFiles()) { FileUtils.moveToDirectory(contentFile, installationDir, false); } if (!newFolder.delete()) { throw new IOException("Failed to delete the nested 'rce' folder (which should be empty at this point)"); } File execfile = new File(installationDir, RCE); if (execfile.exists()) { // If the file "rce" exists, this is a linux platform and we have to make the file executable. final LocalApacheCommandLineExecutor executor = new LocalApacheCommandLineExecutor(installationDir); executor.start(StringUtils.format("chmod +x %s", execfile.getAbsolutePath())); } } /** * Deletes a directory set up with {@link #installFromProductZip(File, File)}. * * @param installationDir the installation directory to delete * @throws IOException on teardown failure; a typical reason is that an instance is still running from this installation */ public void deleteInstallation(File installationDir) throws IOException { // folder must contain rce or rce.exe and p2, plugins and features folders boolean containsRCEExecutable = false; boolean containsP2Folder = false; boolean containsPluginsFolder = false; boolean containsFeaturesFolder = false; for (File file : installationDir.listFiles()) { if (file.isFile() && (file.getName().equals("rce.exe") || file.getName().equals(RCE))) { containsRCEExecutable = true; } else if (file.isDirectory()) { if (file.getName().equals("p2")) { containsP2Folder = true; } else if (file.getName().equals("plugins")) { containsPluginsFolder = true; } else if (file.getName().equals("features")) { containsFeaturesFolder = true; } } } if (containsFeaturesFolder && containsP2Folder && containsPluginsFolder && containsRCEExecutable) { int i = 5; final int waitTime = 100; boolean wasDeleted = false; do { FileSystemOperations.deleteSandboxDirectory(installationDir); if (!installationDir.exists()) { wasDeleted = true; break; } try { Thread.sleep(waitTime); } catch (InterruptedException e) { throw new IOException("Failed to delete installation directory at location " + installationDir.getAbsolutePath() + ". Task was interrupted."); } i--; } while (i > 0); if (!wasDeleted) { throw new IOException("Could not delete installation directory at location " + installationDir.getAbsolutePath() + ". Most likely it is used by another program."); } } else { throw new IOException("Installation directory seems to be no valid RCE installation"); } } private int getFileSize(URL url) throws IOException { HttpURLConnection httpConnection = null; try { httpConnection = (HttpURLConnection) url.openConnection(); httpConnection.setRequestMethod("HEAD"); httpConnection.getInputStream(); return httpConnection.getContentLength(); } finally { if (httpConnection != null) { httpConnection.disconnect(); } else { throw new IOException("Failed to get file size of remote file: " + url.toExternalForm()); } } } public void setUserOutputReceiver(TextOutputReceiver userOutputReceiver) { this.userOutputReceiver = userOutputReceiver; } }