summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Rettberg2015-07-19 15:46:35 +0200
committerSimon Rettberg2015-07-19 15:46:35 +0200
commit79f29a7d95a8eafb0f0a728a745d20bb38c8a3dc (patch)
treef21492d603b99acf4aae8a8789557ba70511e262
parent[client] Async image list loading (diff)
downloadtutor-module-79f29a7d95a8eafb0f0a728a745d20bb38c8a3dc.tar.gz
tutor-module-79f29a7d95a8eafb0f0a728a745d20bb38c8a3dc.tar.xz
tutor-module-79f29a7d95a8eafb0f0a728a745d20bb38c8a3dc.zip
[client] Added listener scheme to the upload task
-rw-r--r--dozentenmodul/src/main/java/org/openslx/dozmod/filetransfer/TransferEvent.java47
-rw-r--r--dozentenmodul/src/main/java/org/openslx/dozmod/filetransfer/TransferEventListener.java5
-rw-r--r--dozentenmodul/src/main/java/org/openslx/dozmod/filetransfer/UploadTask.java140
-rw-r--r--dozentenmodul/src/main/java/org/openslx/dozmod/gui/window/ImageListWindow.java1
-rw-r--r--dozentenmodul/src/main/java/org/openslx/dozmod/gui/wizard/page/ImageUploadPage.java120
-rw-r--r--dozentenmodul/src/main/java/org/openslx/dozmod/state/UploadWizardState.java12
-rw-r--r--dozentenmodul/src/main/java/org/openslx/dozmod/util/FormatHelper.java40
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);
+ }
+
}