package ftp; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.Map; import javax.swing.JOptionPane; import javax.swing.SwingWorker; import models.Image; import models.SessionData; import org.apache.commons.io.FileUtils; import org.apache.log4j.Logger; import org.apache.thrift.TException; import org.openslx.thrifthelper.ThriftManager; import util.ResourceLoader; /** * Execute file download in a background thread and update the progress. * * @author www.codejava.net * */ public class DownloadTask extends SwingWorker { /** * Logger instance for this class. */ private final static Logger LOGGER = Logger.getLogger(DownloadTask.class); private static final int BUFFER_SIZE = 8 * 1024 * 1024; private static final double UPDATE_INTERVAL_SECONDS = 0.6; private static final double UPDATE_INTERVAL_MS = UPDATE_INTERVAL_SECONDS * 1000; private static final double BYTES_PER_MIB = 1024 * 1024; private String host; private int port; private String username; private String password; private String downloadPath; private String saveDir; private int percentCompleted; public DownloadTask(String host, int port, String username, String password, String downloadPath, String saveDir) { this.host = host; this.port = port; this.username = username; this.password = password; this.downloadPath = downloadPath; this.saveDir = saveDir; } /** * Executed in background thread */ @Override protected Void doInBackground() throws Exception { FTPUtility util = new FTPUtility(host, port, username, password); try { util.connect(); // show filesize in the GUI long fileSize = util.getFileSize(downloadPath); firePropertyChange("filesize", 0, fileSize); util.downloadFile(downloadPath); // prepare the input/output streams String fileName = new File(downloadPath).getName(); File downloadFile = new File(saveDir + File.separator + fileName); FileOutputStream outputStream = new FileOutputStream(downloadFile); InputStream inputStream = util.getInputStream(); // initialize the counters needed for speed calculations percentCompleted = 0; byte[] buffer = new byte[BUFFER_SIZE]; int bytesRead = -1; long totalBytesRead = 0; long lastUpdate = 0; long lastBytes = 0; long currentBytes = 0; while ((bytesRead = inputStream.read(buffer)) != -1 && !isCancelled()) { outputStream.write(buffer, 0, bytesRead); currentBytes += bytesRead; totalBytesRead += bytesRead; long now = System.currentTimeMillis(); if (lastUpdate + UPDATE_INTERVAL_MS < now) { percentCompleted = (int) ((totalBytesRead * 100) / fileSize); setProgress(percentCompleted); lastBytes = (lastBytes * 2 + currentBytes) / 3; final double speed = lastBytes / UPDATE_INTERVAL_SECONDS; firePropertyChange("speed", 0, speed / BYTES_PER_MIB); firePropertyChange("bytesread", 0, totalBytesRead); lastUpdate = now; currentBytes = 0; } } // finalize the download by updating the progress bar one last time // (in case we didn't get to do it because of the time interval) percentCompleted = (int) ((totalBytesRead * 100) / fileSize); setProgress(percentCompleted); firePropertyChange("bytesread", 0, totalBytesRead); outputStream.close(); util.finish(); } catch (FTPException ex) { JOptionPane.showMessageDialog(null, "Error downloading file: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); ex.printStackTrace(); setProgress(0); cancel(true); } finally { util.disconnect(); } return null; } /** * Executed in Swing's event dispatching thread */ @Override protected void done() { if (!isCancelled() && percentCompleted == 100) { LOGGER.info("Datei erfolgreich heruntergeladen."); String vmxResult = ""; vmxResult = generateVmx() ? "Passende VMX generiert." : "Keine passende VMX generiert!"; JOptionPane.showMessageDialog(null, "Datei erfolgreich heruntergeladen. " + vmxResult, "Message", JOptionPane.INFORMATION_MESSAGE); } else if (!isCancelled() && percentCompleted != 100) { LOGGER.error("Datei wurde unvollständig heruntergeladen."); JOptionPane.showMessageDialog(null, "Datei wurde unvollständig heruntergeladen. Bitte wiederholen.", "Message", JOptionPane.INFORMATION_MESSAGE); } } /** * Helper to generate the vmx for the downloaded image * * @return true|false indicating the success of the file creation */ private boolean generateVmx() { String vmxTemplate = ResourceLoader.getTextFile("/txt/vmx_template"); // TODO: sanity checks on vmxTemplate would be good here... just to be safe // now we replace the placeholder variables with the real data // for this, we first need to get the image information from the server LOGGER.debug("Image's ID: " + Image.ImageId); Map imageData = null; try { imageData = ThriftManager.getSatClient().getImageData( Image.ImageId, Image.Version, SessionData.authToken); } catch (TException e) { LOGGER.error("Thrift exception during transfer, see trace: ", e); return false; } // sanity check, shouldn't happen. if (imageData == null) { LOGGER.error("Could not query the image information from the server!"); LOGGER.error("Image's ID: " + Image.ImageId); LOGGER.error("Image's version: " + Image.Version); return false; } int hardwareVersion = extractHardwareVersion(saveDir + File.separator + imageData.get("path").replaceFirst("^prod/", "")); if (hardwareVersion == 0) { LOGGER .error("'extractHardwareVersion' returned 0 indicating some problem. See logs."); LOGGER.error("Falling back to default hardware version of '10'."); hardwareVersion = 10; } // TODO: sanity checks on the content of imageData would be good here... // use the information we received about the image vmxTemplate = vmxTemplate.replace("%VM_DISPLAY_NAME%", imageData.get("name")); vmxTemplate = vmxTemplate.replace("%VM_GUEST_OS%", imageData.get("os")); vmxTemplate = vmxTemplate.replace("%VM_CPU_COUNT%", imageData.get("cpu")); vmxTemplate = vmxTemplate.replace("%VM_RAM_SIZE%", String.valueOf(Integer.valueOf(imageData.get("ram")) * 1024)); vmxTemplate = vmxTemplate.replace("%VM_DISK_PATH%", imageData.get("path") .replaceFirst("^prod/", "")); vmxTemplate = vmxTemplate.replace("%VM_HW_VERSION%", String.valueOf(hardwareVersion)); // build filename for the vmx, basicly the same as the path of the vmdk // just without the leading "prod/" and "vmx" instead of "vmdk" at the end. String targetFilename = saveDir + File.separator + imageData.get("path").replaceFirst("^prod/", "") .replaceFirst("\\.vmdk$", "") + ".vmx"; try { // try to write it to file FileUtils.writeStringToFile(new File(targetFilename), vmxTemplate, StandardCharsets.UTF_8); } catch (IOException e) { LOGGER.error("Could not write vmx-template to '" + targetFilename + "'. See trace: ", e); return false; } return true; } /** * Helper to extract the hardware version of the VMDK file by inspecting its * content. * * @return value of hardware version as integer. A return value of 0 indicates * an error. */ private int extractHardwareVersion(String path) { BufferedReader br = null; try { try { br = new BufferedReader( new InputStreamReader(new FileInputStream(path))); String line; // first 4 characters of a VMDK file start with 'KDMV' // first lets check if this is the case line = br.readLine(); if (!line.subSequence(0, 4).equals("KDMV")) { LOGGER .error("Did not see 'KDMV' as first chars of the VMDK! Returning 0."); LOGGER.debug("First line was: " + line); LOGGER.debug("First 4 characters of it: " + line.subSequence(0, 4)); return 0; } // only read a maximum of 20 lines, just in case... int round = 0; while ((line = br.readLine()) != null && round < 20) { if (line.matches("^ddb\\.virtualHWVersion.*")) { String[] tmp = line.split("="); // we should get 2 strings only after the split, lets be sure if (tmp.length != 2) { LOGGER .debug("Splitting returned more than 2 parts, this should not happen!"); return 0; } int candidate = Integer.parseInt(tmp[1].trim().replace("\"", "")); LOGGER.debug("Considering hardware version: " + candidate); if (candidate > 0) { LOGGER .debug("Valid value of the candidate. Using hardware version of: " + candidate); return candidate; } else { LOGGER.error("Candidate is not > 0! Returning 0."); return 0; } } round++; } LOGGER.error("Failed to find hardware version. Tried " + round + " rounds."); } finally { br.close(); } } catch (FileNotFoundException e) { LOGGER.debug("File not found, see trace: ", e); } catch (IOException e) { LOGGER.debug("I/O Exception, see trace: ", e); } return 0; } }