package org.openslx.dozmod.thrift;
import java.awt.Frame;
import java.awt.Window;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.swing.JFileChooser;
import org.apache.log4j.Logger;
import org.apache.thrift.TException;
import org.apache.thrift.transport.TTransportException;
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.ImagePublishData;
import org.openslx.bwlp.thrift.iface.ImageVersionDetails;
import org.openslx.bwlp.thrift.iface.ImageVersionWrite;
import org.openslx.bwlp.thrift.iface.LecturePermissions;
import org.openslx.bwlp.thrift.iface.LectureRead;
import org.openslx.bwlp.thrift.iface.LectureSummary;
import org.openslx.bwlp.thrift.iface.LectureWrite;
import org.openslx.bwlp.thrift.iface.Satellite;
import org.openslx.bwlp.thrift.iface.SatelliteServer.Client;
import org.openslx.bwlp.thrift.iface.TAuthorizationException;
import org.openslx.bwlp.thrift.iface.TInvocationException;
import org.openslx.bwlp.thrift.iface.TNotFoundException;
import org.openslx.bwlp.thrift.iface.TransferInformation;
import org.openslx.bwlp.thrift.iface.TransferState;
import org.openslx.bwlp.thrift.iface.UserInfo;
import org.openslx.bwlp.thrift.iface.WhoamiInfo;
import org.openslx.dozmod.App;
import org.openslx.dozmod.Branding;
import org.openslx.dozmod.Config;
import org.openslx.dozmod.Config.SavedSession;
import org.openslx.dozmod.authentication.Authenticator.AuthenticationData;
import org.openslx.dozmod.filetransfer.DownloadTask;
import org.openslx.dozmod.filetransfer.TransferEvent;
import org.openslx.dozmod.filetransfer.TransferEventListener;
import org.openslx.dozmod.gui.GraphicalCertHandler;
import org.openslx.dozmod.gui.Gui;
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.window.SatelliteListWindow;
import org.openslx.dozmod.thrift.cache.ImageCache;
import org.openslx.dozmod.thrift.cache.LectureCache;
import org.openslx.dozmod.thrift.cache.MetaDataCache;
import org.openslx.dozmod.thrift.cache.UserCache;
import org.openslx.dozmod.util.FormatHelper;
import org.openslx.dozmod.util.VmWrapper;
import org.openslx.dozmod.util.VmWrapper.MetaDataMissingException;
import org.openslx.sat.thrift.version.Version;
import org.openslx.thrifthelper.ThriftManager;
import org.openslx.util.QuickTimer;
import org.openslx.util.QuickTimer.Task;
import org.openslx.util.vm.DiskImage;
import org.openslx.util.vm.DiskImage.UnknownImageFormatException;
public class ThriftActions {
private static final Logger LOGGER = Logger.getLogger(ThriftActions.class);
private static final long SIZE_CHECK_EXTRA_DL = 50l * 1024l * 1024l;
/* *******************************************************************************
*
* SESSION
*
* Session initialization
*
* *******************************************************************************
*/
/**
* @param window the parentWindow
* @param data AuthenticationData as received from a successful login, or
* null if trying to resume a saved sessions
* @param forceCustomSatellite show satellite selection dialog even if there
* is just one
* @param loginWindow
* @return true if initialising the session worked, false otherwise
*/
public static boolean initSession(AuthenticationData data, boolean forceCustomSatellite, Window window) {
final boolean interactive = (data != null);
Client client = null;
WhoamiInfo whoami = null;
String address = null;
String satToken = null;
String masterToken = null;
long remoteVersion = -1;
if (interactive && !forceCustomSatellite && (data.satellites == null || data.satellites.isEmpty())) {
Gui.asyncMessageBox("Login erfolgreich, aber es wurde kein Satellitenserver gefunden.\n"
+ " Bitte geben Sie die Adresse Ihres Servers manuell an.", MessageType.ERROR, LOGGER,
null);
}
do {
if (!interactive) {
// in session resume
SavedSession session = Config.getSavedSession();
if (session == null)
return false;
address = session.address;
satToken = session.token;
masterToken = session.masterToken;
} else {
// determine which sat to connect to
// first check if a sat selection was forced
Satellite sat = null;
if (forceCustomSatellite) {
sat = SatelliteListWindow.open(window, data.satellites);
if (sat == null)
return false;
}
// not forced, do regular sat processing
if (sat == null) {
// if we received only one sat, use that one
if (data.satellites != null && data.satellites.size() == 1) {
sat = data.satellites.get(0);
}
// we had more than one, so check the list
if (sat == null) {
if (!forceCustomSatellite) {
// Remove testing servers (those starting with {x}) if shift was not pressed
for (Iterator<Satellite> it = data.satellites.iterator(); it.hasNext();) {
Satellite entry = it.next();
if (entry.displayName != null && entry.displayName.startsWith("{x}")) {
it.remove();
}
}
}
// after removing the test sats, check if we have only one left and directly use that
// instead of asking the user
if (data.satellites.size() != 1) {
sat = SatelliteListWindow.open(window, data.satellites);
if (sat == null)
return false;
} else {
sat = data.satellites.get(0);
}
}
}
if (sat.addressList == null || sat.addressList.isEmpty()) {
Gui.asyncMessageBox(
"Login erfolgreich, aber für den ausgewählten Satellitenserver ist\n"
+ "keine Adresse hinterlegt. Kann nicht verbinden.", MessageType.ERROR,
LOGGER, null);
continue;
}
address = sat.addressList.get(0);
satToken = data.satelliteToken;
masterToken = data.masterToken;
// In case we loop, make sure the window is shown on the second iteration
forceCustomSatellite = true;
}
// common for resume and fresh login
// try to get a new client
client = ThriftManager.getNewSatelliteClient(GraphicalCertHandler.getSslContext(address),
address, App.THRIFT_SSL_PORT, 5000);
// RPC version check
if (client != null) {
try {
remoteVersion = client.getVersion(Version.VERSION);
} catch (TTransportException e) {
} catch (TException e) {
remoteVersion = 1; // Assume something old
}
}
if (client == null || remoteVersion == -1) {
if (interactive) {
Gui.asyncMessageBox(
"Authentifizierung erfolgreich, die Verbindung zum Satellitenserver ist jedoch nicht möglich.\n\n"
+ "Möglicherweise ist der Server nicht verfügbar, oder die Netzwerkverbindung gestört.",
MessageType.ERROR, null, null);
if (data.satellites.size() == 1) {
return false;
}
continue;
}
return false;
}
if (remoteVersion < Version.MIN_VERSION || remoteVersion > Version.VERSION) {
if (interactive) {
Gui.asyncMessageBox("Das von Ihnen verwendete Dozentenmodul ist nicht mit dem"
+ " gewählten Satellitenserver kompatibel.\n" + "Ihre Version: "
+ Version.VERSION + "\n" + "Satelliten-Version: " + remoteVersion,
MessageType.ERROR, LOGGER, null);
continue;
}
return false;
}
// all good, try to get the whoami info
try {
whoami = client.whoami(satToken);
} catch (TAuthorizationException e) {
if (interactive) {
ThriftError.showMessage(window, LOGGER, e,
"Authentifizierung erfolgreich, der Satellitenserver verweigert jedoch die Verbindung.\n"
+ "Versuchen Sie, sich erneut anzumelden.\n");
}
return false;
} catch (TException e) {
if (interactive) {
ThriftError.showMessage(window, LOGGER, e,
"Authentifizierung erfolgreich, bei der Kommunikation mit"
+ " dem Satellitenserver trat jedoch ein interner Fehler auf.");
continue;
}
return false;
} catch (Exception e) {
if (interactive) {
Gui.asyncMessageBox("Unbekannter Fehler beim Verbinden mit dem Satellitenserver.",
MessageType.ERROR, LOGGER, e);
continue;
}
return false;
}
} while (interactive && whoami == null);
if (whoami != null) {
Session.initialize(whoami, address, satToken, masterToken, remoteVersion);
ThriftManager.setSatelliteAddress(
GraphicalCertHandler.getSslContext(Session.getSatelliteAddress()),
Session.getSatelliteAddress(), App.THRIFT_SSL_PORT, App.THRIFT_TIMEOUT_MS);
QuickTimer.scheduleOnce(new Task() {
@Override
public void fire() {
// Cache useful data from server
MetaDataCache.getOperatingSystems();
MetaDataCache.getVirtualizers();
UserCache.getAll();
ImageCache.get(false);
LectureCache.get(false);
}
});
return true;
}
return false;
}
/* *******************************************************************************
*
* 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 der VM 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 imageBaseId image's id we are writing meta information of
* @param meta actual meta information as ImageBaseWrite
* @throws TException
* @throws TInvocationException
* @throws TNotFoundException
* @throws TAuthorizationException
*/
public static void updateImageBase(final String imageBaseId, final ImageBaseWrite meta)
throws TAuthorizationException, TNotFoundException, TInvocationException, TException {
ThriftManager.getSatClient().updateImageBase(Session.getSatelliteToken(), imageBaseId, meta);
}
/**
* GUI-BLOCKING Pushes the given permission map as custom permission for the
* given imageBaseId
*
* @param imageBaseId image's id we are writing permissions of
* @param permissions actual permissions map to write
* @throws TException
* @throws TInvocationException
* @throws TNotFoundException
* @throws TAuthorizationException
*/
public static void writeImagePermissions(final String imageBaseId,
final Map<String, ImagePermissions> permissions) throws TAuthorizationException,
TNotFoundException, TInvocationException, TException {
ThriftManager.getSatClient().writeImagePermissions(Session.getSatelliteToken(), imageBaseId,
permissions);
}
/* *******************************************************************************
*
* 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
*
* @param frame
* @param transferInformation
* @param versionInfo
* @throws TException
* @throws TInvocationException
* @throws TNotFoundException
* @throws TAuthorizationException
*/
public static void updateImageVersion(final String versionId, final ImageVersionWrite versionInfo)
throws TAuthorizationException, TNotFoundException, TInvocationException, TException {
ThriftManager.getSatClient().updateImageVersion(Session.getSatelliteToken(), versionId, versionInfo);
}
/* *******************************************************************************
*
* 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 Initialises 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 download
* @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 int osId, final long imageSize, final DownloadCallback callback) {
// TODO: Return value? Callback?
QFileChooser fc = new QFileChooser(Config.getDownloadPath(), true);
fc.setDialogTitle("Bitte wählen Sie einen Speicherort");
int action = fc.showSaveDialog(frame);
File selected = fc.getSelectedFile();
if (action != JFileChooser.APPROVE_OPTION || selected == null) {
if (callback != null)
callback.downloadInitialized(false);
return;
}
final File destDir = new File(selected, generateDirname(imageName, imageVersionId));
final File tmpDiskFile = new File(destDir.getAbsolutePath(), VmWrapper.generateFilename(imageName,
null) + ".part");
if (destDir.exists()) {
boolean ret = Gui.showMessageBox(frame, "Verzeichnis '" + destDir.getAbsolutePath()
+ "' existiert bereits, wollen Sie die VM darin überschreiben?",
MessageType.QUESTION_YESNO, LOGGER, null);
if (!ret) {
// user aborted
if (callback != null)
callback.downloadInitialized(false);
return;
}
// delete it
if (!tmpDiskFile.delete() && tmpDiskFile.exists()) {
Gui.showMessageBox(frame, "Datei konnte nicht überschrieben werden!", MessageType.ERROR,
LOGGER, null);
if (callback != null)
callback.downloadInitialized(false);
return;
}
} else {
destDir.getAbsoluteFile().mkdirs();
}
// Check the free space on disk
if (destDir.getUsableSpace() < imageSize + SIZE_CHECK_EXTRA_DL) {
Gui.showMessageBox(frame, "Nicht genügend Speicherplatz im ausgewählten Verzeichnis verfügbar.\n"
+ "Brauche: " + FormatHelper.bytes(imageSize + SIZE_CHECK_EXTRA_DL, false) + "\n"
+ "Habe: " + FormatHelper.bytes(destDir.getUsableSpace(), false), MessageType.ERROR,
LOGGER, null);
if (callback != null)
callback.downloadInitialized(false);
return;
}
QuickTimer.scheduleOnce(new Task() {
@Override
public void fire() {
// since this function (initDownload) supports both download from the satellite and from the masterserver
// we simply try to download from the sat first and if that fails, we try to download from the masterserver
// this has the nice side effect, that if a download from the masterserver is requested, it tries
// to download that image from the sat first (which is probably faster than from the masterserver)
TException transEx = null;
String transHost = null;
TransferInformation transInf = null;
try {
transInf = ThriftManager.getSatClient().requestDownload(Session.getSatelliteToken(),
imageVersionId);
transHost = Session.getSatelliteAddress();
} catch (TException e) {
transEx = e;
}
if (transInf == null) {
// satellite denied download, try master
transHost = null;
try {
transInf = ThriftManager.getMasterClient().downloadImage(Session.getSatelliteToken(),
imageVersionId);
transHost = App.getMasterServerAddress();
} catch (TException e) {
transEx = e;
}
}
if (transInf == null) {
// both download request failed, show user feedback
ThriftError.showMessage(frame, LOGGER, transEx, "Die Download-Anfrage ist gescheitert");
if (callback != null)
callback.downloadInitialized(false);
return;
}
final String fTransHost = transHost;
final TransferInformation fTransInf = transInf;
final DownloadTask dlTask;
try {
dlTask = new DownloadTask(fTransHost, transInf.getPlainPort(),
transInf.getToken(), tmpDiskFile, imageSize, null);
} catch (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;
}
dlTask.setMinConnections(Config.getTransferConnectionCount());
dlTask.addListener(new TransferEventListener() {
@Override
public void update(TransferEvent event) {
if (event.state != TransferState.FINISHED)
return;
DiskImage diskImage = null;
String ext = virtualizerId;
try {
diskImage = new DiskImage(tmpDiskFile);
} catch (IOException | UnknownImageFormatException e) {
LOGGER.warn("Could not open downloaded image for analyze step", e);
}
if (diskImage != null) {
if (diskImage.format != null) {
ext = diskImage.format.extension;
}
if (diskImage.isCompressed) {
String msg = "<html>Die heruntergeladene VM '" + imageName + "' ist ein komprimiertes "
+ "Abbild.<br>Sie müssen das Abbild dekomprimieren, bevor Sie es verändern "
+ "können.<br> Die VM wird lokal voraussichtlich nicht startfähig sein!"
+ "<br><br>Bitte lesen Sie die Hinweise unter "
+ "<a href=\"" + Branding.getServiceFAQWebsite() + "\">"
+ "VMDK Disk Types</a>";
Gui.asyncMessageBox(msg, MessageType.WARNING, null,
null);
}
}
File destImage = new File(destDir.getAbsolutePath(), VmWrapper.generateFilename(
imageName, ext));
destImage.delete();
if (!tmpDiskFile.renameTo(destImage)) {
destImage = tmpDiskFile; // Must be Windows...
}
try {
VmWrapper.wrapVm(destImage, imageName, fTransInf.getMachineDescription(),
virtualizerId, osId, diskImage);
} catch (MetaDataMissingException | IOException e) {
Gui.asyncMessageBox(
"Zur heruntergeladenen VM konnte keine vmx-Datei angelegt werden."
+ "\nSie können versuchen, das Abbild manuell in den VMWare-Player zu importieren.",
MessageType.WARNING, LOGGER, e);
}
}
});
Gui.asyncExec(new Runnable() {
@Override
public void run() {
MainWindow.addDownload(imageName, tmpDiskFile.getName(), dlTask);
}
});
new Thread(dlTask).start();
Config.setDownloadPath(destDir.getParentFile().getAbsolutePath());
if (callback != null)
callback.downloadInitialized(true);
}
});
}
/**
* Helper to generate a directory name for the given imageName and
* imageVersionId.
* Uses the given parameters to build a hopefully unique name as it takes
* the first 8
* chars of the version id...
*
* @param imageName name of the image
* @param imageVersionId version id
* @return generated directory name as String
*/
private static String generateDirname(String imageName, String imageVersionId) {
String fileName = imageName.replaceAll("[^a-zA-Z0-9_\\.\\-]+", "_");
if (fileName.length() > 50) {
fileName = fileName.substring(0, 50);
}
fileName += "--" + imageVersionId.substring(0, 8);
return fileName;
}
/* *******************************************************************************
*
* IMAGE METADATA
*
* *******************************************************************************/
/**
* Callback interface for image meta calls.
*
*/
public interface ImageMetaCallback {
void fetchedImageDetails(ImageDetailsRead details, Map<String, ImagePermissions> permissions);
}
/**
* BLOCKING Gets the image details (w/o permissions) of the given
* imageBaseID
*
* @param frame to display user feedback on
* @param imageBaseId image's id to get the details of
* @return details as ImageDetailsRead if fetching worked, null otherwise
*/
public static ImageDetailsRead getImageDetails(final Frame frame, final String imageBaseId) {
ImageDetailsRead details = null;
try {
details = ThriftManager.getSatClient().getImageDetails(Session.getSatelliteToken(), imageBaseId);
} catch (TException e) {
ThriftError.showMessage(frame, LOGGER, e, "Fehler beim Lesen der Metadaten");
}
return details;
}
/**
* NON-BLOCKING Gets the image details (without permissions) of the given
* imageBaseId.
* Will return the details (or null if fetching failed) back to the
* gui-thread
* through the given callback
*
* @param frame to display user feedback on
* @param imageBaseId image's id to get the details of
* @param callback interface to return the details back to the gui
*/
public static void getImageDetails(final Frame frame, final String imageBaseId,
final ImageMetaCallback callback) {
QuickTimer.scheduleOnce(new Task() {
ImageDetailsRead details = null;
@Override
public void fire() {
details = ThriftActions.getImageDetails(frame, imageBaseId);
Gui.asyncExec(new Runnable() {
@Override
public void run() {
if (callback != null) {
callback.fetchedImageDetails(details, null);
}
}
});
}
});
}
/**
* BLOCKING Gets the data for image with UUID imageBaseId
*
* @param imageBaseId
* @return ImagePublishData image's data if sucessful, null otherwise.
*/
public static ImagePublishData getImageData(final String imageBaseId) {
ImagePublishData data = null;
try {
data = ThriftManager.getMasterClient().getImageData(Session.getSatelliteToken(), imageBaseId);
} catch (TException e) {
LOGGER.error("Could not query sat for ImagePublishData for '" + imageBaseId + "':", e);
}
return data;
}
/**
* NON-BLOCKING Gets the user-specific permission list for the given
* imageBaseId
*
* @param frame to display user feedback on
* @param imageBaseId image's id to get the permission list of
* @param callback interface to return the permission list back to the gui
*/
public static void getImagePermissions(final Frame frame, final String imageBaseId,
final ImageMetaCallback callback) {
QuickTimer.scheduleOnce(new Task() {
Map<String, ImagePermissions> permissionMap = null;
@Override
public void fire() {
permissionMap = ThriftActions.getImagePermissions(frame, imageBaseId);
Gui.asyncExec(new Runnable() {
@Override
public void run() {
if (callback != null) {
callback.fetchedImageDetails(null, permissionMap);
}
}
});
}
});
}
/**
* BLOCKING Gets the user-specific permission list for the given imageBaseId
*
* @param frame to display user feedback on
* @param imageBaseId image's base id to get the user permissions of
* @return the map userid-permissions if fetching worked, null otherwise
*/
public static Map<String, ImagePermissions> getImagePermissions(final Frame frame,
final String imageBaseId) {
Map<String, ImagePermissions> permissionMap = null;
try {
permissionMap = ThriftManager.getSatClient().getImagePermissions(Session.getSatelliteToken(),
imageBaseId);
} catch (TException e) {
ThriftError.showMessage(frame, LOGGER, e, "Fehler beim Lesen der Metadaten");
}
return permissionMap;
}
/**
* BLOCKING Sets the owner of the given lectureId to newOwner
*
* @param frame to display user feedback on
* @param lectureId lecture's id to set the new owner of
* @param newOwner as UserInfo
* @return true if it worked, false otherwise
*/
public static boolean setImageOwner(final Frame frame, final String lectureId, final UserInfo newOwner) {
try {
ThriftManager.getSatClient().setImageOwner(Session.getSatelliteToken(), lectureId,
newOwner.getUserId());
} catch (TException e) {
ThriftError.showMessage(frame, LOGGER, e, "Fehler beim Übertragen der Besitzrechte");
return false;
}
return true;
}
/* *******************************************************************************
*
* IMAGE / VERSION DELETION
*
* *******************************************************************************/
/**
* 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);
}
/**
* BLOCKING Deletes the base image with the given id. Will call the given
* callback to report about the status of this operation
*
* @param frame to display user feedback on
* @param imageBaseId image base id to delete
*/
public static void deleteImageBase(final Frame frame, final String imageBaseId) {
if (imageBaseId == null || imageBaseId.isEmpty())
return;
// first look if we have versions
ImageDetailsRead details = null;
// these help construct the question text for the user, bit ugly...
List<LectureSummary> lecturesToBeDeleted = null;
List<ImageVersionDetails> versionToBeDeleted = null;
try {
details = ThriftManager.getSatClient().getImageDetails(Session.getSatelliteToken(), imageBaseId);
List<LectureSummary> lectureList = ThriftManager.getSatClient().getLectureList(
Session.getSatelliteToken(), 100);
for (LectureSummary lecture : lectureList) {
if (imageBaseId.equals(lecture.getImageBaseId())) {
if (lecturesToBeDeleted == null)
lecturesToBeDeleted = new ArrayList<LectureSummary>();
lecturesToBeDeleted.add(lecture);
}
}
for (ImageVersionDetails version : details.getVersions()) {
if (version.isValid) {
if (versionToBeDeleted == null)
versionToBeDeleted = new ArrayList<ImageVersionDetails>();
versionToBeDeleted.add(version);
}
}
} catch (TException e) {
ThriftError.showMessage(frame, LOGGER, e,
"Fehler beim Holen der Versionen/Veranstaltung zu folgender VM: " + imageBaseId);
return;
}
String questionText;
if (versionToBeDeleted != null && !versionToBeDeleted.isEmpty()) {
questionText = "Die VM \"" + details.getImageName() + "\" hat folgende gültige Versionen:\n";
for (ImageVersionDetails version : versionToBeDeleted) {
questionText += version.getVersionId() + "\n";
}
questionText += "\n";
} else {
questionText = "";
}
if (lecturesToBeDeleted != null && !lecturesToBeDeleted.isEmpty()) {
questionText += "Folgende Veranstaltungen sind mit dieser VM verknüpft: \n";
for (LectureSummary lecture : lecturesToBeDeleted) {
questionText += lecture.getLectureName() + "\n";
}
questionText += "\n";
}
questionText += "Wollen Sie wirklich mit dem Löschen fortfahren?";
if (!userConfirmed(frame, questionText))
return;
try {
ThriftManager.getSatClient().deleteImageBase(Session.getSatelliteToken(), imageBaseId);
} catch (TException e) {
ThriftError.showMessage(frame, LOGGER, e, "Konnte VM-Daten nicht löschen!");
}
}
/**
* 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 version 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 deleteImageVersion(final Frame frame, final ImageVersionDetails version,
final DeleteCallback callback) {
if (version == null || version.versionId == null || version.versionId.isEmpty())
return;
String versionId = version.versionId;
boolean success = false;
List<LectureSummary> lectureList = null;
try {
// fetch lectures
lectureList = ThriftManager.getSatClient().getLectureList(Session.getSatelliteToken(), 100);
} catch (TException e) {
ThriftError.showMessage(frame, LOGGER, e, "Fehler beim Holen der Liste der Veranstaltungen");
if (callback != null)
callback.isDeleted(success);
return;
}
String questionText = "";
// represents if we found matches of not, needed to make a proper
// message
boolean matches = false;
if (lectureList != null && !lectureList.isEmpty()) {
for (LectureSummary lecture : lectureList) {
if (versionId.equals(lecture.getImageVersionId())) {
if (!matches)
questionText = "Diese Version ist zu folgende Veranstaltungen verknüpft:\n";
matches = true;
questionText += lecture.getLectureName() + "\n";
}
}
if (matches)
questionText += "\nWollen Sie diese Version samt Veranstaltungen löschen?\n";
}
if (!matches)
questionText = "Wollen Sie die VM-Image-Version vom "
+ FormatHelper.shortDate(version.createTime) + " Uhr wirklich löschen?";
if (!userConfirmed(frame, questionText))
return;
try {
ThriftManager.getSatClient().deleteImageVersion(Session.getSatelliteToken(), versionId);
LOGGER.info("Deleted version '" + versionId + "'.");
success = true;
} catch (TException e) {
ThriftError.showMessage(frame, LOGGER, e, "Fehler beim Löschen der Version");
if (callback != null)
callback.isDeleted(success);
return;
}
final boolean fSuccess = success;
Gui.asyncExec(new Runnable() {
@Override
public void run() {
if (callback != null)
callback.isDeleted(fSuccess);
}
});
}
/* *******************************************************************************
*
* LECTURE CREATION
*
* *******************************************************************************/
/**
* BLOCKING 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;
}
/**
* 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;
}
/* *******************************************************************************
*
* LECTURE METADATA
*
* *******************************************************************************/
/**
* Interface for GUI-classes running async lecture meta calls
*/
public interface LectureMetaCallback {
void fetchedLectureAndImageDetails(LectureRead lecture, ImageDetailsRead image);
}
/**
* NON-BLOCKING Gets the lecture and image details of the given lectureId.
* IF the lecture does not exist, both fields returned will be null. If the
* lecture exists, the image can still be null, since not all lectures link
* to an image at all times.
*
* @param frame to display user feedback on
* @param lectureId lecture to get the full details of
* @param callback interface to return the details to the gui
*/
public static void getLectureAndImageDetails(final Frame frame, final String lectureId,
final LectureMetaCallback callback) {
QuickTimer.scheduleOnce(new Task() {
@Override
public void fire() {
final LectureRead lecture = ThriftActions.getLectureDetails(frame, lectureId);
final ImageDetailsRead fImage;
if (lecture != null && lecture.imageBaseId != null) {
fImage = ThriftActions.getImageDetails(frame, lecture.getImageBaseId());
} else {
fImage = null;
}
Gui.asyncExec(new Runnable() {
@Override
public void run() {
if (callback != null) {
callback.fetchedLectureAndImageDetails(lecture, fImage);
}
}
});
}
});
}
/**
* BLOCKING Gets the lecture details of the lecture with given lectureId
*
* @param frame to display user feedback on
* @param lectureId lecture to get the details of
* @return LectureRead if fetching details worked, null otherwise
*/
public static LectureRead getLectureDetails(final Frame frame, final String lectureId) {
LectureRead lecture = null;
try {
lecture = ThriftManager.getSatClient().getLectureDetails(Session.getSatelliteToken(), lectureId);
} catch (TException e) {
ThriftError.showMessage(frame, LOGGER, e, "Konnte Veranstaltungdaten nicht abrufen");
}
return lecture;
}
/**
* BLOCKING Updates the lecture with given lectureId to the given lecture
*
* @param frame to show user feedback on
* @param lectureId lecture's is to update
* @param lecture LectureWrite data to update the lecture with
* @return
*/
public static boolean updateLecture(final Frame frame, final String lectureId,
final LectureWrite lectureWrite) {
try {
ThriftManager.getSatClient().updateLecture(Session.getSatelliteToken(), lectureId, lectureWrite);
} catch (TException e) {
ThriftError.showMessage(frame, LOGGER, e, "Fehler beim Updaten der Veranstaltung");
return false;
}
return true;
}
/**
* BLOCKING Gets the list of user-specific permission for the lecture with
* the
* given lectureId
*
* @param frame to display user feedback on
* @param lectureId lecture's id to get the permission list of
* @return the map of the userid-permissions if fetching worked, null
* otherwise
*/
public static Map<String, LecturePermissions> getLecturePermissions(final Frame frame,
final String lectureId) {
Map<String, LecturePermissions> permissions = null;
try {
permissions = ThriftManager.getSatClient().getLecturePermissions(Session.getSatelliteToken(),
lectureId);
} catch (TException e) {
ThriftError.showMessage(frame, LOGGER, e, "Konnte Veranstaltungdaten nicht abrufen");
}
return permissions;
}
/**
* BLOCKING Sets the owner of the given lectureId to newOwner
*
* @param frame to display user feedback on
* @param lectureId lecture's id to set the new owner of
* @param newOwner as UserInfo
* @return true if it worked, false otherwise
*/
public static boolean setLectureOwner(final Frame frame, final String lectureId, final UserInfo newOwner) {
try {
ThriftManager.getSatClient().setLectureOwner(Session.getSatelliteToken(), lectureId,
newOwner.getUserId());
} catch (TException e) {
ThriftError.showMessage(frame, LOGGER, e, "Fehler beim Übertragen der Besitzrechte");
return false;
}
return true;
}
/* *******************************************************************************
*
* LECTURE DELETION
*
* *******************************************************************************/
/**
* Callback interface reporting the status of the deletion back to the gui
*/
public interface DeleteLectureCallback {
void deleted(Map<LectureSummary, TException> failedLectures);
}
/**
* NON-BLOCKING Deletes the lectures of the given list
*
* @param frame to display user feedback on
* @param list of lectures to be deleted
* @param callback interface to report the lectures, which haven't been
* deleted.
*/
public static void deleteLecture(final Frame frame, final List<LectureSummary> lectures,
final DeleteLectureCallback callback) {
if (lectures == null)
return;
String messageText = lectures.size() == 1 ? "Wollen Sie diese Veranstaltung wirklich löschen?"
: "Wollen Sie die " + lectures.size() + " Veranstaltungen wirklich löschen?";
if (!userConfirmed(frame, messageText))
return;
QuickTimer.scheduleOnce(new Task() {
Map<LectureSummary, TException> failedLectures = new HashMap<LectureSummary, TException>();
@Override
public void fire() {
for (final LectureSummary l : lectures) {
if (l == null)
continue;
try {
ThriftManager.getSatClient().deleteLecture(Session.getSatelliteToken(), l.lectureId);
} catch (TException e) {
failedLectures.put(l, e);
}
}
Gui.asyncExec(new Runnable() {
@Override
public void run() {
if (callback != null) {
callback.deleted(failedLectures);
}
}
});
}
});
}
/* *******************************************************************************
*
* PUBLIC IMAGES
*
* *******************************************************************************/
/**
* BLOCKING Gets the image details of an image on the masterserver
*
* @param imageVersionId
* @return ImageDetailsRead if fetching the details worked, null otherwise.
*/
public static ImageDetailsRead getPublishedImageDetails(final String imageBaseId) {
ImageDetailsRead data = null;
try {
data = ThriftManager.getMasterClient().getImageDetails(Session.getMasterToken(), imageBaseId);
} catch (TException e) {
LOGGER.error("Could not query masterserver for ImageDetailsRead for version '" + imageBaseId + "':", e);
}
return data;
}
/* *******************************************************************************
*
* 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, null, null);
}
}