diff options
author | Simon Rettberg | 2015-07-19 15:46:35 +0200 |
---|---|---|
committer | Simon Rettberg | 2015-07-19 15:46:35 +0200 |
commit | 79f29a7d95a8eafb0f0a728a745d20bb38c8a3dc (patch) | |
tree | f21492d603b99acf4aae8a8789557ba70511e262 | |
parent | [client] Async image list loading (diff) | |
download | tutor-module-79f29a7d95a8eafb0f0a728a745d20bb38c8a3dc.tar.gz tutor-module-79f29a7d95a8eafb0f0a728a745d20bb38c8a3dc.tar.xz tutor-module-79f29a7d95a8eafb0f0a728a745d20bb38c8a3dc.zip |
[client] Added listener scheme to the upload task
7 files changed, 259 insertions, 106 deletions
diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/filetransfer/TransferEvent.java b/dozentenmodul/src/main/java/org/openslx/dozmod/filetransfer/TransferEvent.java new file mode 100644 index 00000000..ff86e3ac --- /dev/null +++ b/dozentenmodul/src/main/java/org/openslx/dozmod/filetransfer/TransferEvent.java @@ -0,0 +1,47 @@ +package org.openslx.dozmod.filetransfer; + +import org.openslx.bwlp.thrift.iface.TransferState; +import org.openslx.dozmod.util.FormatHelper; + +public class TransferEvent { + + /** + * Block-based progress of transfer + */ + public final byte[] progress; + /** + * Speed of transfer, human readable + */ + public final String speed; + /** + * Estimated remaining time, human readable + */ + public final String remaining; + /** + * Speed of transfer, in bytes per second + */ + public final long speedRaw; + /** + * Estimated remaining time, in milliseconds + */ + public final long remainingRaw; + /** + * State of the transfer + */ + public final TransferState state; + /** + * An optional error message + */ + public final String errorMessage; + + public TransferEvent(TransferState state, byte[] progress, long speedRaw, long remainingRaw, String errorMessage) { + this.state = state; + this.progress = progress; + this.speedRaw = speedRaw; + this.remainingRaw = remainingRaw; + this.speed = FormatHelper.byteToGigabyte(speedRaw, false) + "/s"; + this.remaining = FormatHelper.formatMilliseconds(remainingRaw); + this.errorMessage = errorMessage; + } + +} diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/filetransfer/TransferEventListener.java b/dozentenmodul/src/main/java/org/openslx/dozmod/filetransfer/TransferEventListener.java new file mode 100644 index 00000000..cbc3b362 --- /dev/null +++ b/dozentenmodul/src/main/java/org/openslx/dozmod/filetransfer/TransferEventListener.java @@ -0,0 +1,5 @@ +package org.openslx.dozmod.filetransfer; + +public interface TransferEventListener { + void update(TransferEvent event); +} diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/filetransfer/UploadTask.java b/dozentenmodul/src/main/java/org/openslx/dozmod/filetransfer/UploadTask.java index 94576ef9..87e8845c 100644 --- a/dozentenmodul/src/main/java/org/openslx/dozmod/filetransfer/UploadTask.java +++ b/dozentenmodul/src/main/java/org/openslx/dozmod/filetransfer/UploadTask.java @@ -2,17 +2,23 @@ package org.openslx.dozmod.filetransfer; import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import org.apache.log4j.Logger; +import org.apache.thrift.TException; +import org.openslx.bwlp.thrift.iface.TInvalidTokenException; +import org.openslx.bwlp.thrift.iface.TransferState; +import org.openslx.bwlp.thrift.iface.TransferStatus; import org.openslx.filetransfer.UploadStatusCallback; import org.openslx.filetransfer.Uploader; +import org.openslx.thrifthelper.ThriftManager; +import org.openslx.util.QuickTimer; +import org.openslx.util.QuickTimer.Task; /** * Executes the file upload in a background thread and updates progress to - * listeners that implement the java.beans.PropertyChangeListener interface. - * - * @author www.codejava.net - * + * listeners. */ public class UploadTask implements Runnable { @@ -21,14 +27,28 @@ public class UploadTask implements Runnable { */ private final static Logger LOGGER = Logger.getLogger(UploadTask.class); + /** + * Update interval of transfer status (speed only) + */ 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 static final long UPDATE_INTERVAL_MS = (long) (UPDATE_INTERVAL_SECONDS * 1000); + /** + * Update interval of the block progress (needs thrift call to sat) + */ + private static final double THRIFT_INTERVAL_SECONDS = 1.7; + private static final long THRIFT_INTERVAL_MS = (long) (THRIFT_INTERVAL_SECONDS * 1000); private final String host; private final int port; private final String uploadToken; private final File uploadFile; + + Uploader upload = null; + + /** + * List of listeners that want to get status updates about the transfer. + */ + private final List<TransferEventListener> listeners = new ArrayList<>(); public UploadTask(String host, int port, String uploadToken, File uploadFile) { // TODO: SSL @@ -43,10 +63,12 @@ public class UploadTask implements Runnable { */ @Override public void run() { - - Uploader upload = null; try { - upload = new Uploader(host, port, null, uploadToken); // TODO: SSL + synchronized (this) { + if (upload != null) + throw new IllegalStateException("Cannot launch UploadTask twice!"); + upload = new Uploader(host, port, null, uploadToken); // TODO: SSL + } final Uploader uploader = upload; final long fileSize = uploadFile.length(); @@ -54,6 +76,7 @@ public class UploadTask implements Runnable { // progress counter private long totalBytesRead = 0; // initialize the counters needed for speed calculations + private long lastThriftUpdate = 0; private long lastUpdate = 0; private long lastBytes = 0; private long currentBytes = 0; @@ -64,29 +87,79 @@ public class UploadTask implements Runnable { currentBytes += bytesSent; final long now = System.currentTimeMillis(); if (lastUpdate + UPDATE_INTERVAL_MS < now) { - final int percentCompleted = (int) ((totalBytesRead * 100) / fileSize); + // Calculate updated speed lastBytes = (lastBytes * 2 + currentBytes) / 3; - final double speed = lastBytes / UPDATE_INTERVAL_SECONDS; - LOGGER.debug(percentCompleted + "% complete (speed: " + speed/BYTES_PER_MIB + ", total: " + totalBytesRead + ")"); + final long speed = (1000 * lastBytes) / (now - lastUpdate); + final long timeRemaining = (fileSize - totalBytesRead) / (speed + 1); + // Reset counters lastUpdate = now; currentBytes = 0; + // Fire event to all listeners + QuickTimer.scheduleOnce(new Task() { + @Override + public void fire() { + TransferState state = null; + byte[] blocks = null; + String error = null; + if (lastThriftUpdate + THRIFT_INTERVAL_MS < now) { + lastThriftUpdate = now; + try { + TransferStatus uploadStatus = ThriftManager.getSatClient().queryUploadStatus(uploadToken); + state = uploadStatus.getState(); + blocks = uploadStatus.getBlockStatus(); + } catch (TInvalidTokenException e) { + error = "Upload token unknown!?"; + } catch (TException e) { + error = "Exception quering upload status: " + e.toString(); + } + } + TransferEvent event = new TransferEvent(state, blocks, speed, timeRemaining, error); + fireEvent(event); + } + }); } } @Override public void uploadError(String message) { - LOGGER.error("Upload error: " + message); - // TODO + TransferEvent event = new TransferEvent(null, null, 0, 0, message); + fireEvent(event); } }); - // if the upload succeeded, set the progress to 100% manually again here to make - // sure the GUI knows about it. - - if (ret) - LOGGER.info("Upload completed."); - else - LOGGER.info("Upload failed."); + if (!ret) { + TransferEvent event = new TransferEvent(TransferState.ERROR, null, 0, 0, null); + fireEvent(event); + return; + } + // Upload succeeded. Let's still wait until the sat reports FINISHED + // Do this in a blocking manner + for (;;) { + TransferState state = null; + byte[] blocks = null; + String error = null; + try { + TransferStatus uploadStatus = ThriftManager.getSatClient() + .queryUploadStatus(uploadToken); + state = uploadStatus.getState(); + blocks = uploadStatus.getBlockStatus(); + } catch (TInvalidTokenException e) { + error = "Upload token unknown!?"; + } catch (TException e) { + error = "Exception quering upload status: " + e.toString(); + } + TransferEvent event = new TransferEvent(state, blocks, 0, 0, error); + fireEvent(event); + if (state != TransferState.WORKING) + break; + try { + Thread.sleep(THRIFT_INTERVAL_MS); + } catch (InterruptedException e) { + event = new TransferEvent(TransferState.ERROR, null, 0, 0, "Upload thread interrupted"); + fireEvent(event); + break; + } + } } catch (IOException e) { LOGGER.error("Upload of " + uploadFile.getAbsolutePath() + " failed: ", e); } finally { @@ -95,4 +168,29 @@ public class UploadTask implements Runnable { } return; } + + private void fireEvent(TransferEvent event) { + synchronized (listeners) { + for (TransferEventListener listener : listeners) { + listener.update(event); + } + } + } + + public void addListener(TransferEventListener listener) { + synchronized (listeners) { + listeners.add(listener); + } + } + + public void removeListener(TransferEventListener listener) { + synchronized (listeners) { + while (listeners.remove(listener)){} + } + } + + public void cancel() { + if (upload != null) + upload.close("Cancelled by user"); + } } diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/window/ImageListWindow.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/window/ImageListWindow.java index 8f9ba21e..49cd89ac 100644 --- a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/window/ImageListWindow.java +++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/window/ImageListWindow.java @@ -39,7 +39,6 @@ public class ImageListWindow extends ImageListWindowLayout { private final static Logger LOGGER = Logger.getLogger(ImageListWindow.class); - public final ImageListWindow me = this; public ImageListWindow(final Shell mainShell) { super(mainShell); diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/wizard/page/ImageUploadPage.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/wizard/page/ImageUploadPage.java index dc88e7b8..3dff8405 100644 --- a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/wizard/page/ImageUploadPage.java +++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/wizard/page/ImageUploadPage.java @@ -14,8 +14,9 @@ import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.FileDialog; -import org.openslx.bwlp.thrift.iface.TransferState; import org.openslx.dozmod.Config; +import org.openslx.dozmod.filetransfer.TransferEvent; +import org.openslx.dozmod.filetransfer.TransferEventListener; import org.openslx.dozmod.filetransfer.UploadTask; import org.openslx.dozmod.gui.Gui; import org.openslx.dozmod.gui.MainWindow; @@ -251,16 +252,30 @@ public class ImageUploadPage extends ImageUploadPageLayout { LOGGER.error("Error while requesting download for: " + uploadWizardState.uuid, e); } } - if (uploadWizardState.transferInformation != null + if (uploadWizardState.uploadTask == null && uploadWizardState.transferInformation != null && uploadWizardState.uuid != null) { // do actually start the upload now LOGGER.debug("Starting upload for : " + uploadWizardState.diskFile.toPath()); - uploadWizardState.uploadTaskThread = new Thread(new UploadTask( + uploadWizardState.uploadTask = new UploadTask( Session.getSatelliteAddress(), uploadWizardState.transferInformation.getPlainPort(), uploadWizardState.transferInformation.getToken(), - uploadWizardState.diskFile)); - uploadWizardState.uploadTaskThread.start(); + uploadWizardState.diskFile); + // -- add listener for upload status/progress -- + uploadWizardState.uploadTask.addListener(new TransferEventListener() { + @Override + public void update(final TransferEvent event) { + final TransferEventListener listener = this; + Gui.display.asyncExec(new Runnable() { + @Override + public void run() { + // always callback to gui + processTransferStatus(listener, event); + } + }); + } + }); + new Thread(uploadWizardState.uploadTask).start(); } Gui.display.asyncExec(new Runnable() { @Override @@ -283,88 +298,50 @@ public class ImageUploadPage extends ImageUploadPageLayout { * know which step failed and handle the error accordingly. */ public void createAndUploadImageCallback() { - // if we get here, reset cancelled to false - uploadWizardState.cancelled = false; + if (getShell().isDisposed()) + return; // always disable "Browse" and "Image Name" field imageNameTextField.setEnabled(false); imageFileBrowseButton.setEnabled(false); + imageFileTextField.setEnabled(false); // -- check image creation -- - if (uploadWizardState.uuid != null) { - // we just created the image, remember uuid - LOGGER.debug("Received UUID from satellite: " + uploadWizardState.uuid); - uploadWizardState.uuid = uploadWizardState.uuid; - setMessage("Image auf dem Satelliten erstellt. Sende Anfrage für Upload..."); - } else { + if (uploadWizardState.uuid == null) { LOGGER.debug("No UUID in upload state!"); setErrorMessage("Konnte das Image nicht auf dem Satelliten erstellen!"); - } - // -- check request upload -- - if (uploadWizardState.transferInformation != null) { - // request "granted" - LOGGER.debug("Transfer request granted."); - uploadWizardState.transferInformation = uploadWizardState.transferInformation; - blockProgressBar.setEnabled(true); - blockProgressBar.setVisible(true); - cancelUpload.setEnabled(true); - cancelUpload.setVisible(true); - setMessage("Upload läuft! Sie können fortfahren."); - } else { + } else if (uploadWizardState.transferInformation == null) { LOGGER.debug("No transfer information in upload state!"); setErrorMessage("Fehler bei der Upload-Anfrage!"); + } else { + cancelUpload.setVisible(true); + blockProgressBar.setVisible(true); + setMessage("Der Upload wurde gestartet"); } - // -- periodically check upload status -- - QuickTimer.scheduleAtFixedRate(new Task() { - final Task me = this; - @Override - public void fire() { - if (uploadWizardState.transferInformation == null) { - LOGGER.debug("Transfer information was reseted, aborting."); - cancel(); - return; - } - try { - uploadWizardState.transferStatus = ThriftManager.getSatClient() - .queryUploadStatus(uploadWizardState.transferInformation.getToken()); - } catch (Exception e) { - LOGGER.error("Error while requesting upload status for: " + uploadWizardState.uuid, e); - } - Gui.display.asyncExec(new Runnable() { - @Override - public void run() { - // should we stop? - if (uploadWizardState.cancelled - || uploadWizardState.transferStatus.state == TransferState.FINISHED -// || uploadWizardState.transferStatus.state == TransferState.CANCELLED - || uploadWizardState.transferStatus.state == TransferState.ERROR) { - me.cancel(); - } - // always callback to gui - processTransferStatus(); - } - }); - } - }, 0, 1000); - setPageComplete(uploadWizardState.uuid != null - && uploadWizardState.transferInformation != null); + setPageComplete(uploadWizardState.uploadTask != null); } /** * Evaluates the transfer's state and show feedback to the user based on the state. + * @param listener */ - private void processTransferStatus() { - if (getControl().isDisposed()) + private void processTransferStatus(TransferEventListener listener, TransferEvent event) { + if (getControl().isDisposed()) { + if (uploadWizardState.uploadTask != null) { + uploadWizardState.uploadTask.removeListener(listener); + } return; + } // always update progress bar - blockProgressBar.setStatus(uploadWizardState.transferStatus.getBlockStatus()); - switch (uploadWizardState.transferStatus.state) { + blockProgressBar.setStatus(event.progress); + // TODO: Move to central listener when we have it + switch (event.state) { case FINISHED: cancelUpload.setEnabled(false); MainWindow.showMessageBox("Upload abgeschlossen.", MessageType.INFO, LOGGER, null); break; case ERROR: - if (MainWindow.showMessageBox("Fehler beim Upload. Nochmal versuchen?", MessageType.ERROR_RETRY, LOGGER, null)) { + if (MainWindow.showMessageBox("Fehler beim Upload: " + event.errorMessage + "\nNochmal versuchen?", MessageType.ERROR_RETRY, LOGGER, null)) { // user wants to try again, just reset transferInformation ... - uploadWizardState.transferInformation = null; + uploadWizardState.uploadTask = null; // ... since this function will then do the upload process again. createAndUploadImage(); } @@ -375,7 +352,7 @@ public class ImageUploadPage extends ImageUploadPageLayout { // WORKING|IDLE = noops? break; default: - LOGGER.debug("Invalid transfer state: " + uploadWizardState.transferStatus.state); + LOGGER.warn("Unhandled transfer state: " + event.state); break; } } @@ -386,16 +363,11 @@ public class ImageUploadPage extends ImageUploadPageLayout { try { ThriftManager.getSatClient().cancelUpload(uploadWizardState.transferInformation.getToken()); } catch (Exception e) { - LOGGER.error("Error while canceling upload for: " + uploadWizardState.uuid, e); - MainWindow.showMessageBox("Konnte Upload nicht abbrechen!", MessageType.ERROR_RETRY, LOGGER, null); + LOGGER.debug("Remote error while canceling upload for " + uploadWizardState.uuid, e); } - // HACK stop our periodic timer - // TODO remove this once we have a CANCELLED state. - uploadWizardState.cancelled = true; - uploadWizardState.transferInformation = null; + uploadWizardState.uploadTask.cancel(); // now try to stop our upload thread - uploadWizardState.uploadTaskThread.stop(); cancelUpload.setEnabled(false); - blockProgressBar.setEnabled(false); + blockProgressBar.setVisible(false); } } diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/state/UploadWizardState.java b/dozentenmodul/src/main/java/org/openslx/dozmod/state/UploadWizardState.java index d37465f6..f6e622bb 100644 --- a/dozentenmodul/src/main/java/org/openslx/dozmod/state/UploadWizardState.java +++ b/dozentenmodul/src/main/java/org/openslx/dozmod/state/UploadWizardState.java @@ -6,8 +6,8 @@ import org.openslx.bwlp.thrift.iface.ImagePermissions; import org.openslx.bwlp.thrift.iface.OperatingSystem; import org.openslx.bwlp.thrift.iface.ShareMode; import org.openslx.bwlp.thrift.iface.TransferInformation; -import org.openslx.bwlp.thrift.iface.TransferStatus; import org.openslx.bwlp.thrift.iface.Virtualizer; +import org.openslx.dozmod.filetransfer.UploadTask; public class UploadWizardState { // -- objects of the GUI itself -- @@ -38,10 +38,8 @@ public class UploadWizardState { public String uuid = null; // transfer information for upload received if the upload request was granted public TransferInformation transferInformation = null; - // transfer status for the current upload - public TransferStatus transferStatus = null; - // thread for executing the upload task - public Thread uploadTaskThread = null; - // user cancelled flag - public boolean cancelled = false; + /** + * The upload task representing this new VM + */ + public UploadTask uploadTask = null; } diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/util/FormatHelper.java b/dozentenmodul/src/main/java/org/openslx/dozmod/util/FormatHelper.java index 2fae918e..02449e4e 100644 --- a/dozentenmodul/src/main/java/org/openslx/dozmod/util/FormatHelper.java +++ b/dozentenmodul/src/main/java/org/openslx/dozmod/util/FormatHelper.java @@ -5,6 +5,8 @@ import org.joda.time.format.DateTimeFormatter; import org.openslx.bwlp.thrift.iface.UserInfo; public class FormatHelper { + + private static final char THIN_SP = '\u202F'; private static final DateTimeFormatter shortDateFormatter = DateTimeFormat.forPattern("dd.MM.yy HH:mm"); private static final DateTimeFormatter longDateFormatter = DateTimeFormat.forPattern("dd.MM.yyyy HH:mm:ss"); @@ -56,10 +58,10 @@ public class FormatHelper { public static String byteToGigabyte(long bytes, boolean si) { int unit = si ? 1000 : 1024; if (bytes < unit) - return bytes + "\u202FB"; + return bytes + THIN_SP + "B"; int exp = (int) (Math.log(bytes) / Math.log(unit)); - String pre = (si ? "kMGTPE" : "KMGTPEZY").charAt(exp - 1) + (si ? "" : "i"); - return String.format("%.1f\u202F%sB", bytes / Math.pow(unit, exp), pre); + String pre = (si ? "kMGTPEZY" : "KMGTPEZY").charAt(exp - 1) + (si ? "" : "i"); + return String.format("%.1f" + THIN_SP + "%sB", bytes / Math.pow(unit, exp), pre); } /** @@ -79,4 +81,36 @@ public class FormatHelper { } } + public static String formatMilliseconds(long ms) { + String ret = ""; + long seconds = ms / 1000; + if (seconds >= 86400 * 365) { // More than a year... + long years = seconds / (86400 * 365); + ret = Long.toString(years) + THIN_SP; + if (years == 1) { + ret += "Jahr"; + } else { + ret += "Jahre"; + } + seconds -= years * 86400 * 365; + } + if (seconds >= 86400) { + long days = seconds / 86400; + if (!ret.isEmpty()) { + ret += ", "; + } + ret += Long.toString(days) + THIN_SP; + if (days == 1) { + ret += "Tag"; + } else { + ret += "Tage"; + } + seconds -= days * 86400; + } + if (!ret.isEmpty()) { + ret += ", "; + } + return ret + String.format("%02d:%02d:%02d", seconds / 3600, (seconds % 3600) / 60, seconds % 60); + } + } |