package org.openslx.dozmod.thrift;
import java.awt.Frame;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Map;
import javax.swing.JFileChooser;
import org.apache.log4j.Logger;
import org.apache.thrift.TException;
import org.openslx.bwlp.thrift.iface.ImageBaseWrite;
import org.openslx.bwlp.thrift.iface.ImageDetailsRead;
import org.openslx.bwlp.thrift.iface.ImagePermissions;
import org.openslx.bwlp.thrift.iface.ImageVersionWrite;
import org.openslx.bwlp.thrift.iface.LecturePermissions;
import org.openslx.bwlp.thrift.iface.LectureWrite;
import org.openslx.bwlp.thrift.iface.TAuthorizationException;
import org.openslx.bwlp.thrift.iface.TImageDataException;
import org.openslx.bwlp.thrift.iface.TransferInformation;
import org.openslx.dozmod.Config;
import org.openslx.dozmod.filetransfer.AsyncHashGenerator;
import org.openslx.dozmod.filetransfer.DownloadTask;
import org.openslx.dozmod.filetransfer.UploadTask;
import org.openslx.dozmod.gui.Gui;
import org.openslx.dozmod.gui.Gui.GuiCallable;
import org.openslx.dozmod.gui.MainWindow;
import org.openslx.dozmod.gui.helper.MessageType;
import org.openslx.dozmod.gui.helper.QFileChooser;
import org.openslx.dozmod.gui.wizard.LectureWizard;
import org.openslx.dozmod.thrift.cache.MetaDataCache;
import org.openslx.thrifthelper.ThriftManager;
import org.openslx.util.QuickTimer;
import org.openslx.util.QuickTimer.Task;
import org.openslx.util.Util;
public class ThriftActions {
private static final Logger LOGGER = Logger.getLogger(ThriftActions.class);
/* *******************************************************************************
*
* IMAGE VERSION DOWNLOAD
*
* Download image version action composed of the interface 'DownloadCallback'
* and the actual static method 'initDownload' to start the download.
*
********************************************************************************/
/**
* The callback interface to inform the GUI about the status of the operation
*/
public interface DownloadCallback {
void downloadInitialized(boolean success);
}
/**
* NON-BLOCKING
* Initializes the download of the given imageVersionId saving it to the given
* imageName
*
* @param frame caller of this method
* @param imageVersionId image version id to download
* @param imageName destination file name
* @param virtualizerId id of the virtualizer
* @param imageSize size in bytes of the image to be downloaded
* @param callback callback function to return status of this operation to the GUI
*/
public static void initDownload(final Frame frame, final String imageVersionId, final String imageName,
final String virtualizerId, final long imageSize, final DownloadCallback callback) {
QFileChooser fc = new QFileChooser(Config.getDownloadPath(), true);
fc.setDialogTitle("Bitte wählen Sie einen Speicherort");
int action = fc.showSaveDialog(frame);
final File file = fc.getSelectedFile();
if (action != JFileChooser.APPROVE_OPTION || file == null)
return;
QuickTimer.scheduleOnce(new Task() {
@Override
public void fire() {
final TransferInformation transInf;
try {
transInf = ThriftManager.getSatClient()
.requestDownload(Session.getSatelliteToken(), imageVersionId);
} catch (TException e) {
ThriftError.showMessage(frame, LOGGER, e, "Die Download-Anfrage ist gescheitert");
if (callback != null)
callback.downloadInitialized(false);
return;
}
File df = null;
try {
file.getAbsoluteFile().mkdirs();
df = new File(file.getAbsolutePath(), generateFilename(imageName, virtualizerId));
final File ff = df;
if (df.exists()) {
if (!Gui.syncExec(new GuiCallable<Boolean>() {
@Override
public Boolean run() {
return Gui.showMessageBox(frame,
"Datei '" + ff.getAbsolutePath() + "' existiert bereits, wollen Sie sie überschreiben?", MessageType.QUESTION_YESNO, LOGGER, null);
}
})) {
// user aborted
return;
} else {
// delete it
if (!df.delete()) {
// TODO what?
Gui.showMessageBox(frame, "Datei konnte nicht überschrieben werden!", MessageType.ERROR, LOGGER, null);
return;
}
}
}
df.createNewFile();
} catch (IOException e) {
LOGGER.warn("Cannot prepare download destination", e);
}
final File destFile = df;
final DownloadTask dlTask;
try {
dlTask = new DownloadTask(Session.getSatelliteAddress(), transInf.getPlainPort(), transInf
.getToken(), destFile, imageSize, null);
} catch (final FileNotFoundException e) {
Gui.asyncMessageBox("Konnte Download nicht vorbereiten: Der gewählte Zielort ist nicht beschreibbar",
MessageType.ERROR, LOGGER, e);
if (callback != null)
callback.downloadInitialized(false);
return;
}
new Thread(dlTask).start();
Gui.asyncExec(new Runnable() {
@Override
public void run() {
Config.setDownloadPath(file.getAbsolutePath());
MainWindow.addDownload(imageName, destFile.getName(), dlTask);
if (callback != null)
callback.downloadInitialized(true);
}
});
}
});
}
/**
* Generates a filename based on the given imageName and with the proper extension
* depending on the virtualizer
*
* @param imageName
* @param virtualizerId
* @return the generated name as String
*/
private static String generateFilename(String imageName, String virtualizerId) {
String fileName = imageName.replaceAll("[^a-zA-Z0-9_\\.\\-]+", "_");
if (fileName.length() > 50) {
fileName = fileName.substring(0, 50);
}
if ("vmware".equals(virtualizerId)) {
fileName += ".vmdk";
} else if ("virtualbox".equals(virtualizerId)) {
fileName += ".vdi";
} else {
fileName += ".img";
}
return fileName;
}
/* *******************************************************************************
*
* IMAGE CREATION
*
* Creates a base image with the given name
*
********************************************************************************/
/**
* GUI-BLOCKING
* Creates the image with the given name. Returns the uuid returned by the server
*
* @param frame calling this action
* @return uuid as String, or null if the creation failed
*/
public static String createImage(final Frame frame, final String name) {
String uuid = null;
try {
uuid = ThriftManager.getSatClient().createImage(Session.getSatelliteToken(), name);
} catch (TException e) {
ThriftError.showMessage(frame, LOGGER, e, "Erstellen des Images fehlgeschlagen");
} catch (Exception e) {
Gui.showMessageBox(frame, "Unbekannter Fehler beim Erstellen der VM", MessageType.ERROR,
LOGGER, e);
}
return uuid;
}
/**
* GUI-BLOCKING
* Pushes a new image base to the server with the given imageBaseId and the meta information in meta
*
* @param frame to show user feedback on
* @param imageBaseId image's id we are writing meta information of
* @param meta actual meta information as ImageBaseWrite
*/
public static void updateImageBase(final Frame frame, final String imageBaseId, final ImageBaseWrite meta) {
try {
ThriftManager.getSatClient().updateImageBase(Session.getSatelliteToken(),
imageBaseId, meta);
} catch (TException e) {
ThriftError.showMessage(frame, LOGGER, e, "Konnte Metadaten des Images nicht übertragen");
}
}
/**
* GUI-BLOCKING
* Pushes the given permission map as custom permission for the given imageBaseId
*
* @param frame to show user feedback on
* @param imageBaseId image's id we are writing permissions of
* @param permissions actual permissions map to write
*/
public static void writeImagePermissions(final Frame frame, final String imageBaseId, final Map<String, ImagePermissions> permissions) {
try {
ThriftManager.getSatClient().writeImagePermissions(Session.getSatelliteToken(),
imageBaseId, permissions);
} catch (TException e) {
ThriftError.showMessage(frame, LOGGER, e, "Konnte Berechtigungen nicht übertragen");
}
}
/* *******************************************************************************
*
* IMAGE VERSION UPLOAD
*
* Methods to upload an image version. This is compromised of two distinct steps:
* - requestVersionUpload(..) to request the upload at the server
* - initUpload(..) to actually start the upload of the file
*
********************************************************************************/
/**
* GUI-BLOCKING
* Request the upload of an image version. Returns the TransferInformation received by the server
* or null if the request failed. Will give user feedback about failures.
*
* @param frame caller of this method
* @param imageBaseId uuid of the image to upload a version of
* @param fileSize size in bytes(?) of the uploaded file
* @param blockHashes
* @param machineDescription
* @param callback
* @return TransferInformation received by the server, null if the request failed.
*/
public static TransferInformation requestVersionUpload(final Frame frame, final String imageBaseId,
final long fileSize, final List<ByteBuffer> blockHashes,
final ByteBuffer machineDescription) {
TransferInformation ti = null;
try {
ti = ThriftManager.getSatClient().requestImageVersionUpload(
Session.getSatelliteToken(),
imageBaseId,
fileSize,
null, // TODO remove deprecated parameter
machineDescription);
LOGGER.info("Version upload granted, versionId: '" + ti.toString());
} catch (TAuthorizationException e) {
ThriftError.showMessage(frame, LOGGER, e, "Upload einer neuen Version nicht erlaubt!");
} catch (TException e) {
ThriftError.showMessage(frame, LOGGER, e, "Upload-Anfrage gescheitert!");
}
return ti;
}
/**
* GUI-BLOCKING
* Starts uploading the given diskFile using the transferInformation and hashGen
*
* @param frame caller of this method
* @param transferInformation transfer information to use for the upload
* @param hashGen hash generator for this file
* @param diskFile the file to upload
* @return UploadTask if the uploading initialized, or null if uploading failed
*/
public static UploadTask initUpload(final Frame frame, final TransferInformation transferInformation,
final File diskFile) {
UploadTask uploadTask = null;
// do actually start the upload now
LOGGER.debug("Starting upload for: " + diskFile.toPath());
try {
uploadTask = new UploadTask(Session.getSatelliteAddress(),
transferInformation.getPlainPort(), transferInformation.getToken(),
diskFile);
} catch (FileNotFoundException e) {
Gui.asyncMessageBox("Kann VM nicht hochladen: Datei nicht gefunden\n\n"
+ diskFile.getAbsolutePath(), MessageType.ERROR, LOGGER, e);
return null;
}
AsyncHashGenerator hashGen = null;
try {
hashGen = new AsyncHashGenerator(transferInformation.token, diskFile);
hashGen.start();
Util.sleep(50); // A little ugly... Give the hash generator a head start
} catch (FileNotFoundException | NoSuchAlgorithmException e) {
Gui.showMessageBox(frame, "Kann keine Block-Hashes für den Upload berechnen, "
+ "automatische Fehlerkorrektur deaktiviert.", MessageType.WARNING, LOGGER, e);
}
Util.sleep(50); // A little ugly... Give the hash generator a head start
Thread uploadThread = new Thread(uploadTask);
uploadThread.setDaemon(true);
uploadThread.start();
do { // Even more ugly - block the GUI thread so we know whether the upload started, and only then switch to the next page
Util.sleep(5);
} while (uploadTask.getFailCount() == 0 && uploadTask.getTransferCount() == 0
&& !uploadTask.isCanceled());
if (uploadTask.getTransferCount() == 0) {
Gui.asyncMessageBox("Aufbau der Verbindung zum Hochladen fehlgeschlagen", MessageType.ERROR,
LOGGER, null);
hashGen.cancel();
uploadTask.cancel();
uploadTask = null;
}
return uploadTask;
}
/**
* GUI-BLOCKING
* Gives user feedback
* TODO
* @param frame
* @param transferInformation
* @param versionInfo
*/
public static void updateImageVersion(final Frame frame,
final String versionId, final ImageVersionWrite versionInfo){
try {
ThriftManager.getSatClient().updateImageVersion(Session.getSatelliteToken(),
versionId,
versionInfo);
} catch (TException e) {
Gui.showMessageBox(frame, "Konnte neue Version nicht erstellen!",
MessageType.ERROR, LOGGER, e);
return;
}
Gui.showMessageBox(frame, "Neue Version erfolgreich erstellt", MessageType.INFO, LOGGER, null);
}
/* *******************************************************************************
*
* IMAGE METADATA QUERY
*
* Fetches image details or permissions
*
********************************************************************************/
public interface MetadataCallback {
void fetchedImageDetails(ImageDetailsRead details);
void fetchedImagePermissions(Map<String, ImagePermissions> permissions);
// void fetchedLectureDetails(LectureRead details);
// void fetchedLecturePermissions(Map<String, ImagePermissions> permissions);
}
public static void getImageDetails(final Frame frame, final String imageBaseId, final MetadataCallback callback) {
QuickTimer.scheduleOnce(new Task() {
ImageDetailsRead details = null;
@Override
public void fire() {
try {
details = ThriftManager.getSatClient().getImageDetails(Session.getSatelliteToken(),
imageBaseId);
} catch (TException e) {
ThriftError.showMessage(frame, LOGGER, e, "Fehler beim Lesen der Metadaten");
if (callback != null)
callback.fetchedImageDetails(details);
return;
}
Gui.asyncExec(new Runnable() {
@Override
public void run() {
if (callback != null) {
callback.fetchedImageDetails(details);
}
}
});
}
});
}
/**
* @param frame
* @param imageBaseId
* @param callback
*/
public static void getImagePermissions(final Frame frame, final String imageBaseId, final MetadataCallback callback) {
QuickTimer.scheduleOnce(new Task() {
Map<String, ImagePermissions> permissionMap = null;
@Override
public void fire() {
try {
permissionMap = ThriftManager.getSatClient().getImagePermissions(Session.getSatelliteToken(), imageBaseId);
LOGGER.debug("Received: " + permissionMap);
if (permissionMap == null)
LOGGER.debug("And NULL");
} catch (TException e) {
ThriftError.showMessage(frame, LOGGER, e, "Fehler beim Lesen der Metadaten");
if (callback != null)
callback.fetchedImagePermissions(null);
return;
}
Gui.asyncExec(new Runnable() {
@Override
public void run() {
if (callback != null) {
callback.fetchedImagePermissions(permissionMap);
}
}
});
}
});
}
/* *******************************************************************************
*
* IMAGE / VERSION DELETION
*
* Deletes a specific image version
*
********************************************************************************/
/**
* Delete callback interface to be implemented by callers of
* ThriftActions.deleteImageVersion(..)
*/
public interface DeleteCallback {
/**
* Called once the status of a delete operation is determined
*
* @param success true if deleted successfully, false otherwise
*/
void isDeleted(boolean success);
}
/**
* NON-BLOCKING
* Deletes either an image base or an image version depending on the parameters.
* To delete an image base, give the imageBaseId and let imageVersionId be null.
* To delete an image version, set both imageBaseId and imageVersionId.
* The success of the operation will be forwarded to the GUI through the DeleteCallback.
*
* @param frame next parent frame of the caller of this method
* @param imageBaseId uuid of the image that belongs to the version
* @param imageVersionId id of the image version to be deleted
* @param callback called to inform the GUI about the deletion status (see DeleteCallback interface)
*/
public static void deleteImageBaseOrVersion(final Frame frame, final String imageBaseId,
final String imageVersionId, final DeleteCallback callback) {
String questionText;
if (imageBaseId == null) {
return;
} else {
questionText = imageVersionId == null ?
"Wollen Sie dieses Image wirklich löschen?" : "Wollen Sie diese Image-Version wirklich löschen?";
}
if (!userConfirmed(frame, questionText))
return;
// perform the deletion
QuickTimer.scheduleOnce(new Task() {
boolean success = false;
@Override
public void fire() {
try {
if (imageVersionId == null) {
// deleting an image base
ThriftManager.getSatClient().deleteImageBase(Session.getSatelliteToken(), imageBaseId);
LOGGER.info("Deleted image with id '" + imageBaseId + "'");
} else {
// deleting an image version
ThriftManager.getSatClient().deleteImageVersion(Session.getSatelliteToken(), imageVersionId);
LOGGER.info("Deleted version '" + imageVersionId + "' of image '" + imageBaseId + "'.");
}
success = true;
} catch (TException e) {
ThriftError.showMessage(frame, LOGGER, e, "Löschen fehlgeschlagen");
if (callback != null)
callback.isDeleted(success);
return;
}
Gui.asyncExec(new Runnable() {
@Override
public void run() {
if (callback != null)
callback.isDeleted(success);
}
});
}
});
}
/* *******************************************************************************
*
* LECTURE CREATION
*
* Methods to create lectures
*
********************************************************************************/
/**
* Creates a lecture with the given meta data
*
* @param frame to show user feedback on
* @param meta actual meta data as LectureWrite
* @return the created lecture's id if it worked, null otherwise
*/
public static String createLecture(final Frame frame, final LectureWrite meta) {
if (meta == null)
return null;
String uuid = null;
try {
// push to sat
uuid = ThriftManager.getSatClient().createLecture(Session.getSatelliteToken(), meta);
} catch (TException e) {
ThriftError.showMessage(frame, LOGGER, e, "Failed to create lecture");
}
return uuid;
}
/**
* GUI-BLOCKING
* Writes custom lecture permissions (permissions param) for the given lectureId.
*
* @param frame to show user feedback on
* @param lectureId lecture's id to write custom permissions for
* @param permissions actual permission map to push
*/
public static boolean writeLecturePermissions(final Frame frame, final String lectureId, final Map<String, LecturePermissions> permissions) {
try {
ThriftManager.getSatClient().writeLecturePermissions(Session.getSatelliteToken(), lectureId, permissions);
} catch (TException e) {
ThriftError.showMessage(frame, LOGGER, e, "Failed to write lecture permissions");
return false;
}
return true;
}
/* *******************************************************************************
*
* PRIVATE HELPERS
*
********************************************************************************/
/**
* Helper to ask the user for confirmation. Returns his choice.
*
* @param frame frame to show this message box on
* @param message question message to display to the user
* @return true if the user confirmed (clicked yes), false otherwise
*/
private static boolean userConfirmed(final Frame frame, final String message) {
return Gui.showMessageBox(frame, message, MessageType.QUESTION_YESNO, LOGGER, null);
}
}