package org.openslx.bwlp.sat.database.mappers;
import java.io.File;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.apache.log4j.Logger;
import org.openslx.bwlp.sat.RuntimeConfig;
import org.openslx.bwlp.sat.database.Database;
import org.openslx.bwlp.sat.database.MysqlConnection;
import org.openslx.bwlp.sat.database.MysqlStatement;
import org.openslx.bwlp.sat.database.Paginator;
import org.openslx.bwlp.sat.database.models.LocalImageVersion;
import org.openslx.bwlp.sat.mail.MailGenerator;
import org.openslx.bwlp.sat.permissions.User;
import org.openslx.bwlp.sat.util.FileSystem;
import org.openslx.bwlp.sat.util.Util;
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.ImageSummaryRead;
import org.openslx.bwlp.thrift.iface.ImageVersionDetails;
import org.openslx.bwlp.thrift.iface.ImageVersionWrite;
import org.openslx.bwlp.thrift.iface.ShareMode;
import org.openslx.bwlp.thrift.iface.TNotFoundException;
import org.openslx.bwlp.thrift.iface.UserInfo;
import org.openslx.filetransfer.util.ChunkList;
import org.openslx.util.QuickTimer;
import org.openslx.util.QuickTimer.Task;
public class DbImage {
private static final Logger LOGGER = Logger.getLogger(DbImage.class);
/**
* Get list of all images visible to the given user, optionally filtered by
* the given list of tags.
*
* @param user Instance of {@link UserInfo} representing the user in
* question
* @param tagSearch list of tags an image must have to be included in the
* list.
* @param page page to return
* @return {@link List} of {@link ImageSummaryRead}
* @throws SQLException
*/
public static List<ImageSummaryRead> getAllVisible(UserInfo user, List<String> tagSearch, int page)
throws SQLException {
// TODO: Implement tag search functionality
try (MysqlConnection connection = Database.getConnection()) {
MysqlStatement stmt = connection.prepareStatement("SELECT"
+ " i.imagebaseid, i.latestversionid, i.displayname,"
+ " i.osid, i.virtid, i.createtime, i.updatetime, i.ownerid,"
+ " i.sharemode, i.istemplate, i.canlinkdefault, i.candownloaddefault,"
+ " i.caneditdefault, i.canadmindefault,"
+ " lat.expiretime, lat.filesize, lat.isrestricted, lat.isvalid,"
+ " lat.uploaderid, lat.isprocessed, lat.createtime AS uploadtime,"
+ " perm.canlink, perm.candownload, perm.canedit, perm.canadmin"
+ " FROM imagebase i"
+ " LEFT JOIN imageversion lat ON (lat.imageversionid = i.latestversionid)"
+ " LEFT JOIN imagepermission perm ON (i.imagebaseid = perm.imagebaseid AND perm.userid = :userid)"
+ Paginator.limitStatement(page));
stmt.setString("userid", user.userId);
ResultSet rs = stmt.executeQuery();
List<ImageSummaryRead> list = new ArrayList<>(100);
while (rs.next()) {
list.add(resultSetToSummary(user, rs));
}
return list;
} catch (SQLException e) {
LOGGER.error("Query failed in DbImage.getAllVisible()", e);
throw e;
}
}
public static ImageDetailsRead getImageDetails(UserInfo user, String imageBaseId)
throws TNotFoundException, SQLException {
try (MysqlConnection connection = Database.getConnection()) {
MysqlStatement stmt = connection.prepareStatement("SELECT i.imagebaseid, i.latestversionid,"
+ " i.displayname, i.description, i.osid, i.virtid, i.createtime, i.updatetime, i.ownerid, i.updaterid,"
+ " i.sharemode, i.istemplate,"
+ " i.canlinkdefault, i.candownloaddefault, i.caneditdefault, i.canadmindefault,"
+ " perm.canlink, perm.candownload, perm.canedit, perm.canadmin"
+ " FROM imagebase i"
+ " LEFT JOIN imagepermission perm ON (i.imagebaseid = perm.imagebaseid AND perm.userid = :userid)"
+ " WHERE i.imagebaseid = :imagebaseid");
stmt.setString("userid", user == null ? "-" : user.userId);
stmt.setString("imagebaseid", imageBaseId);
ResultSet rs = stmt.executeQuery();
if (!rs.next())
throw new TNotFoundException();
// Exists:
List<String> tags = DbSoftwareTag.getImageTags(connection, imageBaseId);
List<ImageVersionDetails> versions = getImageVersions(connection, imageBaseId);
ImagePermissions defaultPermissions = DbImagePermissions.fromResultSetDefault(rs);
ImageDetailsRead image = new ImageDetailsRead(rs.getString("imagebaseid"),
rs.getString("latestversionid"), versions, rs.getString("displayname"),
rs.getString("description"), tags, rs.getInt("osid"), rs.getString("virtid"),
rs.getLong("createtime"), rs.getLong("updatetime"), rs.getString("ownerid"),
rs.getString("updaterid"), toShareMode(rs.getString("sharemode")),
rs.getByte("istemplate") != 0, defaultPermissions);
User.setCombinedUserPermissions(image, user);
return image;
} catch (SQLException e) {
LOGGER.error("Query failed in DbImage.getImageDetails()", e);
throw e;
}
}
private final static String localImageBaseSql = "SELECT"
+ " imageversionid, imagebaseid, filepath, filesize, uploaderid, createtime, expiretime, isvalid"
+ " FROM imageversion";
private static LocalImageVersion toLocalImageVersion(ResultSet rs) throws SQLException {
return new LocalImageVersion(rs.getString("imageversionid"), rs.getString("imagebaseid"),
rs.getString("filepath"), rs.getLong("filesize"), rs.getString("uploaderid"),
rs.getLong("createtime"), rs.getLong("expiretime"), rs.getBoolean("isvalid"));
}
public static LocalImageVersion getLocalImageData(String imageVersionId) throws TNotFoundException,
SQLException {
try (MysqlConnection connection = Database.getConnection()) {
MysqlStatement stmt = connection.prepareStatement(localImageBaseSql
+ " WHERE imageversionid = :imageversionid");
stmt.setString("imageversionid", imageVersionId);
ResultSet rs = stmt.executeQuery();
if (!rs.next())
throw new TNotFoundException();
return toLocalImageVersion(rs);
} catch (SQLException e) {
LOGGER.error("Query failed in DbImage.getLocalImageData()", e);
throw e;
}
}
protected static List<LocalImageVersion> getLocalImageVersions(MysqlConnection connection,
String imageBaseId) throws SQLException {
MysqlStatement stmt = connection.prepareStatement(localImageBaseSql
+ " WHERE imagebaseid = :imagebaseid");
stmt.setString("imagebaseid", imageBaseId);
ResultSet rs = stmt.executeQuery();
List<LocalImageVersion> list = new ArrayList<>();
while (rs.next()) {
list.add(toLocalImageVersion(rs));
}
return list;
}
public static List<LocalImageVersion> getExpiringLocalImageVersions(int maxRemainingDays)
throws SQLException {
try (MysqlConnection connection = Database.getConnection()) {
MysqlStatement stmt = connection.prepareStatement(localImageBaseSql
+ " WHERE expiretime < :deadline");
stmt.setLong("deadline", Util.unixTime() + (maxRemainingDays * 86400));
ResultSet rs = stmt.executeQuery();
List<LocalImageVersion> list = new ArrayList<>();
while (rs.next()) {
list.add(toLocalImageVersion(rs));
}
return list;
} catch (SQLException e) {
LOGGER.error("Query failed in DbImage.getAllLocalImages()", e);
throw e;
}
}
/**
* Private helper to create an {@link ImageSummaryRead} instance from a
* {@link ResultSet}
*
* @param rs
* @return
* @throws SQLException
*/
private static ImageSummaryRead resultSetToSummary(UserInfo user, ResultSet rs) throws SQLException {
ImagePermissions defaultPermissions = DbImagePermissions.fromResultSetDefault(rs);
ImageSummaryRead entry = new ImageSummaryRead(rs.getString("imagebaseid"),
rs.getString("latestversionid"), rs.getString("displayname"), rs.getInt("osid"),
rs.getString("virtid"), rs.getLong("createtime"), rs.getLong("updatetime"),
rs.getLong("uploadtime"), rs.getLong("expiretime"), rs.getString("ownerid"),
rs.getString("uploaderid"), toShareMode(rs.getString("sharemode")), rs.getLong("filesize"),
rs.getByte("isrestricted") != 0, rs.getByte("isvalid") != 0, rs.getByte("isprocessed") != 0,
rs.getByte("istemplate") != 0, defaultPermissions);
entry.userPermissions = DbImagePermissions.fromResultSetUser(rs);
User.setCombinedUserPermissions(entry, user);
return entry;
}
/**
* Get summary about an image by its base id.
*
* @param user
* @param imageBaseId
* @return
* @throws SQLException
* @throws TNotFoundException
*/
public static ImageSummaryRead getImageSummary(UserInfo user, String imageBaseId) throws SQLException,
TNotFoundException {
try (MysqlConnection connection = Database.getConnection()) {
return getImageSummary(connection, user, imageBaseId);
} catch (SQLException e) {
LOGGER.error("Query failed in DbImage.getImageSummary()", e);
throw e;
}
}
protected static ImageSummaryRead getImageSummary(MysqlConnection connection, UserInfo user,
String imageBaseId) throws SQLException, TNotFoundException {
MysqlStatement stmt = connection.prepareStatement("SELECT"
+ " i.imagebaseid, i.latestversionid, i.displayname,"
+ " i.osid, i.virtid, i.createtime, i.updatetime, i.ownerid,"
+ " i.sharemode, i.istemplate, i.canlinkdefault, i.candownloaddefault,"
+ " i.caneditdefault, i.canadmindefault,"
+ " lat.expiretime, lat.filesize, lat.isrestricted, lat.isvalid,"
+ " lat.uploaderid, lat.isprocessed, lat.createtime AS uploadtime,"
+ " perm.canlink, perm.candownload, perm.canedit, perm.canadmin"
+ " FROM imagebase i"
+ " LEFT JOIN imageversion lat ON (lat.imageversionid = i.latestversionid)"
+ " LEFT JOIN imagepermission perm ON (i.imagebaseid = perm.imagebaseid AND perm.userid = :userid)"
+ " WHERE i.imagebaseid = :imagebaseid");
stmt.setString("userid", user.userId);
stmt.setString("imagebaseid", imageBaseId);
ResultSet rs = stmt.executeQuery();
if (!rs.next()) {
throw new TNotFoundException();
}
return resultSetToSummary(user, rs);
}
protected static List<ImageVersionDetails> getImageVersions(MysqlConnection connection, String imageBaseId)
throws SQLException {
List<ImageVersionDetails> versionList = new ArrayList<>();
MysqlStatement stmt = connection.prepareStatement("SELECT"
+ " imageversionid, createtime, expiretime, filesize, uploaderid,"
+ " isrestricted, isvalid, isprocessed" + " FROM imageversion"
+ " WHERE imagebaseid = :imagebaseid");
stmt.setString("imagebaseid", imageBaseId);
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
String imageVersionId = rs.getString("imageversionid");
versionList.add(new ImageVersionDetails(imageVersionId, rs.getLong("createtime"),
rs.getLong("expiretime"), rs.getLong("filesize"), rs.getString("uploaderid"),
rs.getByte("isrestricted") != 0, rs.getByte("isvalid") != 0,
rs.getByte("isprocessed") != 0, DbSoftwareTag.getImageVersionSoftwareList(connection,
imageVersionId)));
}
stmt.close();
return versionList;
}
/**
* Create new row in the imagebase table.
*
* @param user the user the image will belong to
* @param imageName name of the image to be created
* @return UUID of the newly created image
* @throws SQLException
*/
public static String createImage(UserInfo user, String imageName) throws SQLException {
if (imageName.length() > 100) {
imageName = imageName.substring(0, 100);
}
try (MysqlConnection connection = Database.getConnection()) {
MysqlStatement stmt = connection.prepareStatement("INSERT INTO imagebase"
+ " (imagebaseid, displayname, createtime, updatetime, ownerid, updaterid, sharemode,"
+ " istemplate, canlinkdefault, candownloaddefault, caneditdefault, canadmindefault)"
+ " VALUES"
+ " (:baseid, :name, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), :userid, :userid, 'LOCAL',"
+ " 0, 0, 0, 0, 0)");
String imageUuid = UUID.randomUUID().toString();
stmt.setString("baseid", imageUuid);
stmt.setString("name", imageName);
stmt.setString("userid", user.userId);
stmt.executeUpdate();
connection.commit();
return imageUuid;
} catch (SQLException e) {
LOGGER.error("Query failed in DbImage.createImage()", e);
throw e;
}
}
/**
* Create or update a base image with the given publish data.
* Used for replication from master server.
*
* @param user The user who triggered the download, and will be considered
* the creator; if null, the creator of the image will be used
* @param image The image to create
* @throws SQLException
*/
public static void writeBaseImage(UserInfo user, ImagePublishData image) throws SQLException {
if (user == null) {
user = image.user;
}
try (MysqlConnection connection = Database.getConnection()) {
MysqlStatement stmt = connection.prepareStatement("INSERT INTO imagebase"
+ " (imagebaseid, displayname, description, osid, virtid, createtime,"
+ " updatetime, ownerid, updaterid, sharemode, istemplate,"
+ " canlinkdefault, candownloaddefault, caneditdefault, canadmindefault)"
+ " VALUES "
+ " (:imagebaseid, :displayname, :description, :osid, :virtid, :unixtime,"
+ " :unixtime, :userid, :userid, :sharemode, :istemplate,"
+ " 1, 1, 0, 0) "
+ " ON DUPLICATE KEY UPDATE "
+ " displayname = VALUES(displayname), description = VALUES(description),"
+ " osid = VALUES(osid), virtid = VALUES(virtid), updatetime = VALUES(updatetime),"
+ " updaterid = VALUES(updaterid), istemplate = VALUES(istemplate)");
stmt.setString("imagebaseid", image.imageBaseId);
stmt.setString("displayname", image.imageName);
stmt.setString("description", image.description);
stmt.setInt("osid", image.osId);
stmt.setString("virtid", image.virtId);
stmt.setLong("unixtime", Util.unixTime());
stmt.setString("userid", user.userId);
stmt.setString("sharemode", "DOWNLOAD");
stmt.setBoolean("istemplate", image.isTemplate);
stmt.executeUpdate();
connection.commit();
} catch (SQLException e) {
LOGGER.error("Query failed in DbImage.writeBaseImage()", e);
throw e;
}
}
public static void updateImageMetadata(UserInfo user, String imageBaseId, ImageBaseWrite image)
throws SQLException {
if (image.imageName.length() > 100) {
image.imageName = image.imageName.substring(0, 100);
}
try (MysqlConnection connection = Database.getConnection()) {
MysqlStatement stmt = connection.prepareStatement("UPDATE imagebase"
+ " SET displayname = :imagename, description = :description,"
+ " osid = :osid, virtid = :virtid,"
+ (user == null || User.isSuperUser(user) ? " istemplate = :istemplate," : "")
+ " canlinkdefault = :canlink,"
+ " candownloaddefault = :candownload, caneditdefault = :canedit,"
+ " updaterid = :updaterid, updatetime = UNIX_TIMESTAMP(),"
+ " canadmindefault = :canadmin" + " WHERE imagebaseid = :baseid");
stmt.setString("baseid", imageBaseId);
stmt.setString("imagename", image.imageName);
stmt.setString("description", image.description);
stmt.setInt("osid", image.osId);
stmt.setString("virtid", image.virtId);
try {
stmt.setBoolean("istemplate", image.isTemplate);
} catch (IllegalArgumentException e) {
// This might not exist in the query, so swallow the exception
}
stmt.setBoolean("canlink", image.defaultPermissions.link);
stmt.setBoolean("candownload", image.defaultPermissions.download);
stmt.setBoolean("canedit", image.defaultPermissions.edit);
stmt.setBoolean("canadmin", image.defaultPermissions.admin);
stmt.setString("updaterid", user.userId);
stmt.executeUpdate();
connection.commit();
} catch (SQLException e) {
LOGGER.error("Query failed in DbImage.updateImageMetadata()", e);
throw e;
}
}
public static void setImageOwner(String imageBaseId, String newOwnerId, UserInfo changingUser)
throws SQLException {
try (MysqlConnection connection = Database.getConnection()) {
MysqlStatement stmt;
if (changingUser == null) {
stmt = connection.prepareStatement("UPDATE imagebase"
+ " SET ownerid = :ownerid WHERE imagebaseid = :baseid");
stmt.setString("ownerid", newOwnerId);
stmt.setString("baseid", imageBaseId);
} else {
stmt = connection.prepareStatement("UPDATE imagebase"
+ " SET ownerid = :ownerid, updaterid = :updaterid, updatetime = UNIX_TIMESTAMP()"
+ " WHERE imagebaseid = :baseid");
stmt.setString("ownerid", newOwnerId);
stmt.setString("updaterid", changingUser.userId);
stmt.setString("baseid", imageBaseId);
}
stmt.executeUpdate();
connection.commit();
} catch (SQLException e) {
LOGGER.error("Query failed in DbImage.setImageOwner()", e);
throw e;
}
}
/**
* Get the UUID of the image base belonging to the given image version UUID.
* Returns <code>null</code> if the UUID does not exist.
*
* @param imageVersionId
* @return
* @throws SQLException
* @throws TNotFoundException
*/
public static String getBaseIdForVersionId(String imageVersionId) throws SQLException, TNotFoundException {
try (MysqlConnection connection = Database.getConnection()) {
return getBaseIdForVersionId(connection, imageVersionId);
} catch (SQLException e) {
LOGGER.error("Query failed in DbImage.getBaseIdForVersionId()", e);
throw e;
}
}
/**
* Get the UUID of the image base belonging to the given image version UUID.
*
* @param imageVersionId
* @return
* @throws SQLException
* @throws TNotFoundException version id is unknown
*/
protected static String getBaseIdForVersionId(MysqlConnection connection, String imageVersionId)
throws SQLException, TNotFoundException {
MysqlStatement stmt = connection.prepareStatement("SELECT imagebaseid FROM imageversion"
+ " WHERE imageversionid = :imageversionid LIMIT 1");
stmt.setString("imageversionid", imageVersionId);
ResultSet rs = stmt.executeQuery();
if (!rs.next())
throw new TNotFoundException();
return rs.getString("imagebaseid");
}
private static ShareMode toShareMode(String string) {
return ShareMode.valueOf(string);
}
/**
* Update meta data of a specific image version.
*
* @param user user doing the edit
* @param imageVersionId UUID of image version
* @param image meta data to set
* @throws SQLException
* @throws TNotFoundException
*/
public static void updateImageVersion(UserInfo user, String imageVersionId, ImageVersionWrite image)
throws SQLException, TNotFoundException {
try (MysqlConnection connection = Database.getConnection()) {
String baseId = getBaseIdForVersionId(connection, imageVersionId);
if (baseId == null)
throw new TNotFoundException();
// First update version table
MysqlStatement stmtVersion = connection.prepareStatement("UPDATE imageversion v SET"
+ " v.isrestricted = :isrestricted" + " WHERE v.imageversionid = :versionid");
stmtVersion.setString("versionid", imageVersionId);
stmtVersion.setBoolean("isrestricted", image.isRestricted);
if (stmtVersion.executeUpdate() != 0) {
// Then base table
MysqlStatement stmtBase = connection.prepareStatement("UPDATE imagebase b SET"
+ " b.updaterid = :userid, b.updatetime = UNIX_TIMESTAMP()"
+ " WHERE b.imagebaseid = :baseid");
stmtBase.setString("userid", user.userId);
stmtBase.setString("baseid", baseId);
stmtBase.executeUpdate();
}
connection.commit();
} catch (SQLException e) {
LOGGER.error("Query failed in DbImage.updateImageVersion()", e);
throw e;
}
}
/**
* Mark given image for deletion. The image is marked for deletion by
* setting the expire timestamp to the current date, and by setting the
* image disabled and invalid. Next time the cleanup task runs, the image
* will be deleted.
*
* @param imageVersionId UUID of image version to delete
* @throws SQLException
* @throws TNotFoundException
*/
public static void markForDeletion(String... imageVersionIds) throws SQLException, TNotFoundException {
if (imageVersionIds == null || imageVersionIds.length == 0)
return;
try (MysqlConnection connection = Database.getConnection()) {
// Disable version in question
MysqlStatement disableStmt = connection.prepareStatement("UPDATE imageversion SET"
+ " expiretime = UNIX_TIMESTAMP() - 86400, isvalid = 0"
+ " WHERE imageversionid = :versionid");
for (String imageVersionId : imageVersionIds) {
disableStmt.setString("versionid", imageVersionId);
disableStmt.executeUpdate();
}
connection.commit();
} catch (SQLException e) {
LOGGER.error("Query failed in DbImage.markForDeletion()", e);
throw e;
}
updateLatestVersionAsync(imageVersionIds);
}
public static void setShareMode(String imageBaseId, ImageBaseWrite newData) throws SQLException {
try (MysqlConnection connection = Database.getConnection()) {
MysqlStatement stmt = connection.prepareStatement("UPDATE imagebase"
+ " SET sharemode = :sharemode WHERE imagebaseid = :baseid LIMIT 1");
stmt.setString("baseid", imageBaseId);
stmt.setString("sharemode", newData.shareMode.toString());
stmt.executeUpdate();
connection.commit();
} catch (SQLException e) {
LOGGER.error("Query failed in DbImage.setShareMode()", e);
throw e;
}
}
public static void createImageVersion(String imageBaseId, String imageVersionId, UserInfo owner,
long fileSize, String filePath, ImageVersionWrite versionSettings, ChunkList chunks,
byte[] machineDescription) throws SQLException {
try (MysqlConnection connection = Database.getConnection()) {
final long nowSecs = Util.unixTime();
final long expireTime = nowSecs + RuntimeConfig.getMaxImageValiditySeconds();
MysqlStatement verStmt = connection.prepareStatement("INSERT INTO imageversion"
+ " (imageversionid, imagebaseid, createtime, expiretime, filesize, filepath, uploaderid,"
+ " isrestricted, isvalid, isprocessed, mastersha1, virtualizerconfig)"
+ " VALUES "
+ " (:imageversionid, :imagebaseid, :createtime, :expiretime, :filesize, :filepath,"
+ " :uploaderid, :isrestricted, :isvalid, :isprocessed, :mastersha1, :virtualizerconfig)");
verStmt.setString("imageversionid", imageVersionId);
verStmt.setString("imagebaseid", imageBaseId);
verStmt.setLong("createtime", nowSecs);
verStmt.setLong("expiretime", expireTime);
verStmt.setLong("filesize", fileSize);
verStmt.setString("filepath", filePath);
verStmt.setString("uploaderid", owner.userId);
verStmt.setBoolean("isrestricted", versionSettings == null ? false : versionSettings.isRestricted);
verStmt.setBoolean("isvalid", true);
verStmt.setBoolean("isprocessed", false);
verStmt.setBinary("mastersha1", null); // TODO
verStmt.setBinary("virtualizerconfig", machineDescription);
verStmt.executeUpdate();
// TODO: Write chunk hashes to DB
// Make this version the latest version
MysqlStatement baseStmt = connection.prepareStatement("UPDATE imagebase SET"
+ " latestversionid = :imageversionid WHERE imagebaseid = :imagebaseid LIMIT 1");
baseStmt.setString("imageversionid", imageVersionId);
baseStmt.setString("imagebaseid", imageBaseId);
baseStmt.executeUpdate();
LocalImageVersion liv = new LocalImageVersion(imageVersionId, imageBaseId, filePath, fileSize,
owner.userId, nowSecs, expireTime, true);
DbLecture.autoUpdateUsedImage(connection, imageBaseId, liv);
setLatestVersion(connection, imageBaseId, liv);
connection.commit();
} catch (SQLException e) {
LOGGER.error("Query failed in DbImage.createImageVersion()", e);
throw e;
}
}
protected static void markValid(MysqlConnection connection, boolean valid,
LocalImageVersion... imageVersion) throws SQLException {
if (imageVersion == null || imageVersion.length == 0)
return;
MysqlStatement stmt = connection.prepareStatement("UPDATE imageversion SET isvalid = :valid"
+ " WHERE imageversionid = :imageversionid");
for (LocalImageVersion version : imageVersion) {
stmt.setString("imageversionid", version.imageVersionId);
stmt.setBoolean("valid", valid);
stmt.executeUpdate();
}
}
public static void markValid(boolean valid, boolean async, LocalImageVersion... imageVersions)
throws SQLException {
if (imageVersions == null || imageVersions.length == 0)
return;
try (MysqlConnection connection = Database.getConnection()) {
markValid(connection, valid, imageVersions);
if (!async) {
updateLatestVersion(connection, imageVersions);
}
connection.commit();
} catch (SQLException e) {
LOGGER.error("Query failed in DbImage.markInvalid()", e);
throw e;
}
if (async) {
updateLatestVersionAsync(imageVersions);
}
}
public static void deletePermanently(LocalImageVersion image) throws SQLException {
try (MysqlConnection connection = Database.getConnection()) {
DbLecture.deletePermanently(connection, image);
MysqlStatement stmt = connection.prepareStatement("DELETE FROM imageversion"
+ " WHERE imageversionid = :imageversionid");
stmt.setString("imageversionid", image.imageVersionId);
stmt.executeUpdate();
connection.commit();
} catch (SQLException e) {
LOGGER.error("Query failed in DbImage.deletePermanently()", e);
throw e;
}
}
private static void updateLatestVersionAsync(final LocalImageVersion... changingVersion) {
if (changingVersion == null || changingVersion.length == 0)
return;
QuickTimer.scheduleOnce(new Task() {
@Override
public void fire() {
try (MysqlConnection connection = Database.getConnection()) {
updateLatestVersion(connection, changingVersion);
connection.commit();
} catch (SQLException e) {
LOGGER.error("Query failed in DbImage.updateLatestVersionAsync()", e);
}
}
});
}
private static void updateLatestVersionAsync(final String... changingImageVersionIds) {
if (changingImageVersionIds == null || changingImageVersionIds.length == 0)
return;
QuickTimer.scheduleOnce(new Task() {
@Override
public void fire() {
try (MysqlConnection connection = Database.getConnection()) {
for (String changingImageVersionId : changingImageVersionIds) {
versionValidityChanged(connection, changingImageVersionId, null);
}
connection.commit();
} catch (SQLException | TNotFoundException e) {
LOGGER.error("Query failed in DbImage.updateLatestVersionAsync()", e);
}
}
});
}
private static void updateLatestVersion(MysqlConnection connection, LocalImageVersion... versions)
throws SQLException {
if (versions == null || versions.length == 0)
return;
for (LocalImageVersion version : versions) {
try {
versionValidityChanged(connection, version.imageVersionId, version.imageBaseId);
} catch (TNotFoundException e) {
// Swallow - logging happens in called method
}
}
}
/**
* Makes sure the latestVersionId-field of the given base image is
* consistent, while also updating any affected lectures.
*
* @param connection mysql connection to use
* @param changingImageVersionId the version id of the image that changed
* (REQUIRED)
* @param imageBaseId the base id of the image that changed (OPTIONAL, will
* be determined if missing)
* @throws TNotFoundException
* @throws SQLException
*/
private static void versionValidityChanged(MysqlConnection connection, String changingImageVersionId,
String imageBaseId) throws TNotFoundException, SQLException {
if (imageBaseId == null) {
imageBaseId = DbImage.getBaseIdForVersionId(connection, changingImageVersionId);
if (imageBaseId == null)
throw new TNotFoundException();
}
// Determine new latest version, as we might have to update the imagebase and lecture tables
List<LocalImageVersion> versions = DbImage.getLocalImageVersions(connection, imageBaseId);
LocalImageVersion latestVersion = null;
LocalImageVersion changingVersion = null;
for (LocalImageVersion version : versions) {
if (version.imageVersionId.equals(changingImageVersionId)) {
changingVersion = version;
}
if (version.isValid && (latestVersion == null || version.createTime > latestVersion.createTime)) {
File versionFile = FileSystem.composeAbsoluteImagePath(version);
if (versionFile != null) {
if (versionFile.canRead() && versionFile.length() == version.fileSize) {
latestVersion = version;
} else {
markValid(connection, false, version);
}
}
}
}
if (changingVersion == null) {
LOGGER.warn("BUG: oldVersion ninjad away on updateLatestVersion (" + changingImageVersionId + ")");
} else {
// Switch any lectures linking to this version if applicable
if (changingVersion.isValid) {
// The version that changed became valid. In case it was the latest version (by date), this is now
// the version to be used by auto-updating lectures. If it wasn't the latest version, the following call will
// do nothing
DbLecture.autoUpdateUsedImage(connection, imageBaseId, latestVersion);
} else {
// The version that changed is now invalid. Switch any lecture using it to the latest
// available version, ignoring the "auto update" flag of the lecture
DbLecture.forcefullySwitchUsedImage(connection, changingVersion, latestVersion);
}
}
// Now update the latestversionid of the baseimage if applicable
if (setLatestVersion(connection, imageBaseId, latestVersion)) {
MailGenerator.versionDeleted(imageBaseId, changingVersion, latestVersion);
}
}
/**
* Set the latest version id of the given base image. Returns true if and
* only if the latest version id of the base image did actually change
* through this call.
*
* @param connection mysql connection to use
* @param imageBaseId base id of image in question
* @param latest image version that is to become the latest version, or
* <code>null</code> if there is no valid version
* @return true if changed to a different, non-null image
* @throws SQLException
*/
private static boolean setLatestVersion(MysqlConnection connection, String imageBaseId,
LocalImageVersion latest) throws SQLException {
// Update latestversionid reference in imagebase table
MysqlStatement latestStmt = connection.prepareStatement("UPDATE imagebase SET latestversionid = :newversionid"
+ " WHERE imagebaseid = :imagebaseid");
latestStmt.setString("newversionid", latest == null ? null : latest.imageVersionId);
latestStmt.setString("imagebaseid", imageBaseId);
// If nothing changed (because the deleted version was not the latest), bail out
if (latestStmt.executeUpdate() == 0)
return false;
// It there is no valid version, bail out as a shortcut - queries below wouldn't do anything
if (latest == null)
return true;
// Latest version changed - update expire dates of related versions
// Set short expire date for versions that are NOT the latest version but are still marked valid
long shortExpire = Util.unixTime() + RuntimeConfig.getOldVersionExpireSeconds();
MysqlStatement oldStmt = connection.prepareStatement("UPDATE imageversion SET"
+ " expiretime = If(expiretime < :shortexpire, expiretime, :shortexpire)"
+ " WHERE imagebaseid = :imagebaseid AND imageversionid <> :imageversionid AND isvalid = 1");
oldStmt.setString("imageversionid", latest.imageVersionId);
oldStmt.setString("imagebaseid", imageBaseId);
oldStmt.setLong("shortexpire", shortExpire);
oldStmt.executeUpdate();
// Now set a long expire date for the latest version, as it might have been shortened before
MysqlStatement newStmt = connection.prepareStatement("UPDATE imageversion SET"
+ " expiretime = If(createtime + :maxvalid > expiretime, createtime + :maxvalid, expiretime)"
+ " WHERE imageversionid = :imageversionid");
newStmt.setString("imageversionid", latest.imageVersionId);
newStmt.setLong("maxvalid", RuntimeConfig.getMaxImageValiditySeconds());
newStmt.executeUpdate();
return true;
}
public static int deleteOrphanedBases() throws SQLException {
try (MysqlConnection connection = Database.getConnection()) {
// Get all image base entries which have no image version
MysqlStatement sel = connection.prepareStatement("SELECT i.imagebaseid FROM imagebase i"
+ " LEFT JOIN imageversion v USING (imagebaseid)"
+ " WHERE i.updatetime < :cutoff AND v.imageversionid IS NULL");
sel.setLong("cutoff", Util.unixTime() - 86400 * 14);
ResultSet rs = sel.executeQuery();
// Now delete them all
MysqlStatement stmt = connection.prepareStatement("DELETE FROM imagebase"
+ " WHERE imagebaseid = :imagebaseid");
int ret = 0;
while (rs.next()) {
String baseId = null;
try {
baseId = rs.getString("imagebaseid");
stmt.setString("imagebaseid", baseId);
ret += stmt.executeUpdate();
} catch (SQLException e) {
LOGGER.warn("Could not delete base image " + baseId, e);
}
}
connection.commit();
return ret;
} catch (SQLException e) {
LOGGER.error("Query failed in DbImage.deleteOrphanedBases()", e);
throw e;
}
}
}