summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Rettberg2015-08-24 18:33:53 +0200
committerSimon Rettberg2015-08-24 18:33:53 +0200
commitff5cf4a6e6e7fd59e79ff097264644fc1605c464 (patch)
tree86feece32cea13507fbd20f904e6988ff4ab92d1
parent[client] Major layout fix (diff)
downloadtutor-module-ff5cf4a6e6e7fd59e79ff097264644fc1605c464.tar.gz
tutor-module-ff5cf4a6e6e7fd59e79ff097264644fc1605c464.tar.xz
tutor-module-ff5cf4a6e6e7fd59e79ff097264644fc1605c464.zip
[server] Handle image version deletion, interface for sending mails (no SMTP yet)
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbImage.java100
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbLecture.java160
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbUser.java60
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/models/LocalImageVersion.java8
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/FileServer.java26
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/maintenance/DeleteOldImages.java4
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/maintenance/Mailer.java161
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/Configuration.java8
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/FileSystem.java102
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/Formatter.java16
10 files changed, 502 insertions, 143 deletions
diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbImage.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbImage.java
index 0f925371..9aee00b8 100644
--- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbImage.java
+++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbImage.java
@@ -14,8 +14,9 @@ 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.fileserv.FileServer;
+import org.openslx.bwlp.sat.maintenance.Mailer;
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;
@@ -86,7 +87,7 @@ public class DbImage {
+ " FROM imagebase i"
+ " LEFT JOIN imagepermission perm ON (i.imagebaseid = perm.imagebaseid AND perm.userid = :userid)"
+ " WHERE i.imagebaseid = :imagebaseid");
- stmt.setString("userid", user.userId);
+ stmt.setString("userid", user == null ? "-" : user.userId);
stmt.setString("imagebaseid", imageBaseId);
ResultSet rs = stmt.executeQuery();
if (!rs.next())
@@ -120,8 +121,8 @@ public class DbImage {
if (!rs.next())
throw new TNotFoundException();
return new LocalImageVersion(rs.getString("imageversionid"), rs.getString("imagebaseid"),
- rs.getString("filepath"), rs.getLong("filesize"), rs.getLong("createtime"), rs.getLong("expiretime"),
- rs.getBoolean("isvalid"));
+ rs.getString("filepath"), rs.getLong("filesize"), rs.getLong("createtime"),
+ rs.getLong("expiretime"), rs.getBoolean("isvalid"));
} catch (SQLException e) {
LOGGER.error("Query failed in DbImage.getLocalImageData()", e);
throw e;
@@ -138,13 +139,14 @@ public class DbImage {
List<LocalImageVersion> list = new ArrayList<>();
while (rs.next()) {
list.add(new LocalImageVersion(rs.getString("imageversionid"), rs.getString("imagebaseid"),
- rs.getString("filepath"), rs.getLong("filesize"), rs.getLong("createtime"), rs.getLong("expiretime"),
- rs.getBoolean("isvalid")));
+ rs.getString("filepath"), rs.getLong("filesize"), rs.getLong("createtime"),
+ rs.getLong("expiretime"), rs.getBoolean("isvalid")));
}
return list;
}
- public static List<LocalImageVersion> getExpiringLocalImageVersions(int maxRemainingDays) throws SQLException {
+ public static List<LocalImageVersion> getExpiringLocalImageVersions(int maxRemainingDays)
+ throws SQLException {
try (MysqlConnection connection = Database.getConnection()) {
MysqlStatement stmt = connection.prepareStatement("SELECT"
+ " imageversionid, imagebaseid, filepath, filesize, createtime, expiretime, isvalid"
@@ -154,8 +156,8 @@ public class DbImage {
List<LocalImageVersion> list = new ArrayList<>();
while (rs.next()) {
list.add(new LocalImageVersion(rs.getString("imageversionid"), rs.getString("imagebaseid"),
- rs.getString("filepath"), rs.getLong("filesize"), rs.getLong("createtime"), rs.getLong("expiretime"),
- rs.getBoolean("isvalid")));
+ rs.getString("filepath"), rs.getLong("filesize"), rs.getLong("createtime"),
+ rs.getLong("expiretime"), rs.getBoolean("isvalid")));
}
return list;
} catch (SQLException e) {
@@ -492,7 +494,8 @@ public class DbImage {
baseStmt.setString("imageversionid", imageVersionId);
baseStmt.setString("imagebaseid", imageBaseId);
baseStmt.executeUpdate();
- LocalImageVersion liv = new LocalImageVersion(imageVersionId, imageBaseId, filePath, fileSize, nowSecs, expireTime, true);
+ LocalImageVersion liv = new LocalImageVersion(imageVersionId, imageBaseId, filePath, fileSize,
+ nowSecs, expireTime, true);
DbLecture.autoUpdateUsedImage(connection, imageBaseId, liv);
setLatestVersion(connection, imageBaseId, liv);
connection.commit();
@@ -502,8 +505,8 @@ public class DbImage {
}
}
- protected static void markValid(MysqlConnection connection, boolean valid, LocalImageVersion... imageVersion)
- throws SQLException {
+ 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"
@@ -515,7 +518,8 @@ public class DbImage {
}
}
- public static void markValid(boolean valid, boolean async, LocalImageVersion... imageVersion) throws SQLException {
+ public static void markValid(boolean valid, boolean async, LocalImageVersion... imageVersion)
+ throws SQLException {
if (imageVersion == null || imageVersion.length == 0)
return;
try (MysqlConnection connection = Database.getConnection()) {
@@ -532,7 +536,7 @@ public class DbImage {
updateLatestVersionAsync(imageVersion);
}
}
-
+
public static void deletePermanently(LocalImageVersion image) throws SQLException {
try (MysqlConnection connection = Database.getConnection()) {
MysqlStatement stmt = connection.prepareStatement("DELETE FROM imageversion"
@@ -545,7 +549,7 @@ public class DbImage {
throw e;
}
}
-
+
private static void updateLatestVersionAsync(final LocalImageVersion... changingVersion) {
if (changingVersion == null || changingVersion.length == 0)
return;
@@ -575,16 +579,14 @@ public class DbImage {
}
});
}
-
- private static void updateLatestVersion(MysqlConnection connection,
- LocalImageVersion... versions) throws
- SQLException {
+
+ 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);
+ versionValidityChanged(connection, version.imageVersionId, version.imageBaseId);
} catch (TNotFoundException e) {
// Swallow - logging happens in called method
}
@@ -612,59 +614,69 @@ public class DbImage {
}
// Determine new latest version, as we might have to update the imagebase and lecture tables
List<LocalImageVersion> versions = DbImage.getLocalImageVersions(connection, imageBaseId);
- LocalImageVersion newVersion = null;
- LocalImageVersion oldVersion = null;
+ LocalImageVersion latestVersion = null;
+ LocalImageVersion changingVersion = null;
for (LocalImageVersion version : versions) {
if (version.imageVersionId.equals(changingImageVersionId)) {
- oldVersion = version;
+ changingVersion = version;
}
- if (version.isValid && (newVersion == null || version.createTime > newVersion.createTime)) {
- File versionFile = FileServer.composeAbsolutePath(version);
- if (versionFile.canRead() && versionFile.length() == version.fileSize) {
- newVersion = version;
- } else {
- markValid(connection, false, 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 (oldVersion == null) {
+ if (changingVersion == null) {
LOGGER.warn("BUG: oldVersion ninjad away on updateLatestVersion (" + changingImageVersionId + ")");
} else {
// Switch any lectures linking to this version if applicable
- if (oldVersion.isValid) {
- DbLecture.autoUpdateUsedImage(connection, imageBaseId, newVersion);
+ 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 {
- DbLecture.forcefullySwitchUsedImage(connection, oldVersion, newVersion);
+ // 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, newVersion)) {
- // TODO: Latest version changed -> mail
- //Something.versionDeleted(imageBaseId, oldVersion, newVersion);
+ if (setLatestVersion(connection, imageBaseId, latestVersion)) {
+ Mailer.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 <b>and</b> the new latest version is <b>not null</b>.
- *
+ * 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
+ * <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 {
+ 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, or the latest version was set to NULL, bail out
- if (latestStmt.executeUpdate() == 0 || latest == null)
+ 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();
diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbLecture.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbLecture.java
index 2623a9b6..f6431b46 100644
--- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbLecture.java
+++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbLecture.java
@@ -4,6 +4,7 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Iterator;
import java.util.List;
import java.util.UUID;
@@ -12,6 +13,7 @@ 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.models.LocalImageVersion;
+import org.openslx.bwlp.sat.maintenance.Mailer;
import org.openslx.bwlp.sat.permissions.User;
import org.openslx.bwlp.sat.util.Json;
import org.openslx.bwlp.thrift.iface.LectureRead;
@@ -129,38 +131,50 @@ public class DbLecture {
}
}
+ private static LectureSummary fillSummary(UserInfo user, ResultSet rs) throws SQLException {
+ LectureSummary lecture = new LectureSummary();
+ lecture.setLectureId(rs.getString("lectureid"));
+ lecture.setLectureName(rs.getString("lecturename"));
+ lecture.setImageVersionId(rs.getString("imageversionid"));
+ lecture.setImageBaseId(rs.getString("imagebaseid"));
+ lecture.setIsEnabled(rs.getBoolean("isenabled"));
+ lecture.setStartTime(rs.getLong("starttime"));
+ lecture.setEndTime(rs.getLong("endtime"));
+ lecture.setLastUsed(rs.getLong("lastused"));
+ lecture.setUseCount(rs.getInt("usecount"));
+ lecture.setOwnerId(rs.getString("ownerid"));
+ lecture.setUpdaterId(rs.getString("updaterid"));
+ lecture.setIsExam(rs.getBoolean("isexam"));
+ lecture.setHasInternetAccess(rs.getBoolean("hasinternetaccess"));
+ lecture.setDefaultPermissions(DbLecturePermissions.fromResultSetDefault(rs));
+ lecture.setUserPermissions(DbLecturePermissions.fromResultSetUser(rs));
+ lecture.setIsImageVersionUsable(rs.getBoolean("imgvalid"));
+ if (user != null) {
+ User.setCombinedUserPermissions(lecture, user);
+ }
+ return lecture;
+ }
+
+ private static final String summaryBaseSql = "SELECT"
+ + " l.lectureid, l.displayname AS lecturename, l.imageversionid, i.imagebaseid,"
+ + " l.isenabled, l.starttime, l.endtime, l.lastused, l.usecount, l.ownerid, l.updaterid,"
+ + " l.isexam, l.hasinternetaccess, l.caneditdefault, l.canadmindefault,"
+ + " i.isvalid AS imgvalid, perm.canedit, perm.canadmin"
+ + " FROM lecture l "
+ + " INNER JOIN imageversion i USING (imageversionid)"
+ + " LEFT JOIN lecturepermission perm ON (perm.lectureid = l.lectureid AND perm.userid = :userid)";
+
public static LectureSummary getLectureSummary(UserInfo user, String lectureId) throws SQLException,
TNotFoundException {
try (MysqlConnection connection = Database.getConnection()) {
- MysqlStatement stmt = connection.prepareStatement("SELECT"
- + " l.lectureid, l.displayname AS lecturename, l.imageversionid, l.isenabled,"
- + " l.starttime, l.endtime, l.lastused, l.usecount, l.ownerid, l.updaterid,"
- + " l.isexam, l.hasinternetaccess, l.caneditdefault, l.canadmindefault,"
- + " perm.canedit, perm.canadmin FROM lecture l"
- + " LEFT JOIN lecturepermission perm ON (perm.lectureid = l.lectureid AND perm.userid = :userid)"
+ MysqlStatement stmt = connection.prepareStatement(summaryBaseSql
+ " WHERE l.lectureid = :lectureid");
stmt.setString("lectureid", lectureId);
- stmt.setString("userid", user.userId);
+ stmt.setString("userid", user == null ? "-" : user.userId);
ResultSet rs = stmt.executeQuery();
if (!rs.next())
throw new TNotFoundException();
- LectureSummary lecture = new LectureSummary();
- lecture.setLectureId(rs.getString("lectureid"));
- lecture.setLectureName(rs.getString("lecturename"));
- lecture.setImageVersionId(rs.getString("imageversionid"));
- lecture.setIsEnabled(rs.getBoolean("isenabled"));
- lecture.setStartTime(rs.getLong("starttime"));
- lecture.setEndTime(rs.getLong("endtime"));
- lecture.setLastUsed(rs.getLong("lastused"));
- lecture.setUseCount(rs.getInt("usecount"));
- lecture.setOwnerId(rs.getString("ownerid"));
- lecture.setUpdaterId(rs.getString("updaterid"));
- lecture.setIsExam(rs.getBoolean("isexam"));
- lecture.setHasInternetAccess(rs.getBoolean("hasinternetaccess"));
- lecture.setDefaultPermissions(DbLecturePermissions.fromResultSetDefault(rs));
- lecture.setUserPermissions(DbLecturePermissions.fromResultSetUser(rs));
- User.setCombinedUserPermissions(lecture, user);
- return lecture;
+ return fillSummary(user, rs);
} catch (SQLException e) {
LOGGER.error("Query failed in DbLecture.getLectureSummary()", e);
throw e;
@@ -169,42 +183,13 @@ public class DbLecture {
public static List<LectureSummary> getAll(UserInfo user, int page) throws SQLException {
try (MysqlConnection connection = Database.getConnection()) {
- MysqlStatement stmt = connection.prepareStatement("SELECT"
- + " l.lectureid, l.displayname AS lecturename, l.imageversionid, i.imagebaseid,"
- + " l.isenabled, l.starttime, l.endtime, l.lastused, l.usecount, l.ownerid, l.updaterid,"
- + " l.isexam, l.hasinternetaccess, l.caneditdefault, l.canadmindefault,"
- + " i.isvalid AS imgvalid, p.canedit, p.canadmin"
- + " FROM lecture l "
- + " INNER JOIN imageversion i USING (imageversionid)"
- + " LEFT JOIN lecturepermission p ON (p.lectureid = l.lectureid AND p.userid = :userid)"
+ MysqlStatement stmt = connection.prepareStatement(summaryBaseSql
+ (User.isStudent(user) ? " WHERE i.isrestricted = 0" : ""));
- if (user == null) {
- stmt.setString("userid", "-");
- } else {
- stmt.setString("userid", user.userId);
- }
+ stmt.setString("userid", user == null ? "-" : user.userId);
ResultSet rs = stmt.executeQuery();
List<LectureSummary> list = new ArrayList<>(100);
while (rs.next()) {
- LectureSummary lecture = new LectureSummary();
- lecture.setLectureId(rs.getString("lectureid"));
- lecture.setLectureName(rs.getString("lecturename"));
- lecture.setImageVersionId(rs.getString("imageversionid"));
- lecture.setImageBaseId(rs.getString("imagebaseid"));
- lecture.setIsEnabled(rs.getBoolean("isenabled"));
- lecture.setStartTime(rs.getLong("starttime"));
- lecture.setEndTime(rs.getLong("endtime"));
- lecture.setLastUsed(rs.getLong("lastused"));
- lecture.setUseCount(rs.getInt("usecount"));
- lecture.setOwnerId(rs.getString("ownerid"));
- lecture.setUpdaterId(rs.getString("updaterid"));
- lecture.setIsExam(rs.getBoolean("isexam"));
- lecture.setHasInternetAccess(rs.getBoolean("hasinternetaccess"));
- lecture.setDefaultPermissions(DbLecturePermissions.fromResultSetDefault(rs));
- lecture.setUserPermissions(DbLecturePermissions.fromResultSetUser(rs));
- lecture.setIsImageVersionUsable(rs.getBoolean("imgvalid"));
- User.setCombinedUserPermissions(lecture, user);
- list.add(lecture);
+ list.add(fillSummary(user, rs));
}
return list;
} catch (SQLException e) {
@@ -213,14 +198,30 @@ public class DbLecture {
}
}
- protected static List<String> getAllUsingImageVersion(MysqlConnection connection, String imageVersionId)
+ protected static List<LectureSummary> getAllUsingImageBase(MysqlConnection connection, String imageBaseId, boolean autoUpdateOnly)
throws SQLException {
- MysqlStatement stmt = connection.prepareStatement("SELECT lectureid FROM lecture WHERE imageversionid = :imageversionid");
+ MysqlStatement stmt = connection.prepareStatement(summaryBaseSql
+ + " WHERE imagebaseid = :imagebaseid" + (autoUpdateOnly ? " AND autoupdate = 1" : ""));
+ stmt.setString("imagebaseid", imageBaseId);
+ stmt.setString("userid", "-");
+ ResultSet rs = stmt.executeQuery();
+ List<LectureSummary> list = new ArrayList<>();
+ while (rs.next()) {
+ list.add(fillSummary(null, rs));
+ }
+ return list;
+ }
+
+ protected static List<LectureSummary> getAllUsingImageVersion(MysqlConnection connection,
+ String imageVersionId) throws SQLException {
+ MysqlStatement stmt = connection.prepareStatement(summaryBaseSql
+ + " WHERE imageversionid = :imageversionid");
stmt.setString("imageversionid", imageVersionId);
+ stmt.setString("userid", "-");
ResultSet rs = stmt.executeQuery();
- List<String> list = new ArrayList<>();
+ List<LectureSummary> list = new ArrayList<>();
while (rs.next()) {
- list.add(rs.getString("lectureid"));
+ list.add(fillSummary(null, rs));
}
return list;
}
@@ -322,8 +323,18 @@ public class DbLecture {
*/
protected static void autoUpdateUsedImage(MysqlConnection connection, String imageBaseId,
LocalImageVersion newVersion) throws SQLException {
- // TODO: select first so we can email
- // TODO: If new version is null, do not change, but send warning mail
+ if (newVersion == null)
+ return;
+ List<LectureSummary> lectures = getAllUsingImageBase(connection, imageBaseId, true);
+ if (lectures.isEmpty())
+ return;
+ // Remove lectures that are already on the given latest version from the list...
+ for (Iterator<LectureSummary> it = lectures.iterator(); it.hasNext();) {
+ LectureSummary lecture = it.next();
+ if (lecture.imageVersionId.equals(newVersion.imageVersionId))
+ it.remove();
+ }
+ // Update lectures in DB
MysqlStatement stmt = connection.prepareStatement("UPDATE lecture l, imageversion v SET"
+ " l.imageversionid = :imageversionid"
+ " WHERE v.imageversionid = l.imageversionid AND v.imagebaseid = :imagebaseid"
@@ -331,6 +342,8 @@ public class DbLecture {
stmt.setString("imageversionid", newVersion.imageVersionId);
stmt.setString("imagebaseid", imageBaseId);
stmt.executeUpdate();
+ // Send informative mail to lecture admins
+ Mailer.lectureAutoUpdate(lectures, newVersion);
}
/**
@@ -343,21 +356,24 @@ public class DbLecture {
|| (newVersion != null && newVersion.imageVersionId.equals(oldVersion.imageVersionId)))
return;
// First, get list of lectures using the image version to switch away from
- List<String> lectures = getAllUsingImageVersion(connection, oldVersion.imageVersionId);
+ List<LectureSummary> lectures = getAllUsingImageVersion(connection, oldVersion.imageVersionId);
if (lectures.isEmpty())
return;
- // TODO: If there is no new candidate to switch to, send a warning via mail
+ // TODO: If there is no new candidate to switch to, send a warning via mail, otherwise, inform about switch
+ MysqlStatement stmt;
if (newVersion == null) {
- // ... email stuff
- return;
+ stmt = connection.prepareStatement("UPDATE lecture SET isenabled = 0 WHERE imageversionid = :oldversionid");
+ stmt.setString("oldversionid", oldVersion.imageVersionId);
+ Mailer.lectureDeactivated(lectures);
+ } else {
+ // Update and send info mail
+ stmt = connection.prepareStatement("UPDATE lecture SET imageversionid = :newversionid"
+ + " WHERE imageversionid = :oldversionid");
+ stmt.setString("oldversionid", oldVersion.imageVersionId);
+ stmt.setString("newversionid", newVersion.imageVersionId);
+ Mailer.lectureForcedUpdate(lectures, newVersion);
}
- // Update and send info mail
- MysqlStatement stmt = connection.prepareStatement("UPDATE lecture SET imageversionid = :newversionid"
- + " WHERE imageversionid = :oldversionid");
- stmt.setString("oldversionid", oldVersion.imageVersionId);
- stmt.setString("newversionid", newVersion.imageVersionId);
stmt.executeUpdate();
- // TODO: Send mails .. Something.sendLectureChangeNotify(lectures, oldVersion, newVersion);
}
}
diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbUser.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbUser.java
index 9c80caf4..4552a1d7 100644
--- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbUser.java
+++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbUser.java
@@ -4,6 +4,8 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;
import org.openslx.bwlp.sat.database.Database;
@@ -11,12 +13,40 @@ 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.LocalUser;
+import org.openslx.bwlp.thrift.iface.TNotFoundException;
import org.openslx.bwlp.thrift.iface.UserInfo;
+import org.openslx.util.TimeoutHashMap;
public class DbUser {
private static final Logger LOGGER = Logger.getLogger(DbUser.class);
+ private static Map<String, UserInfo> userCache;
+
+ private static void initCache() throws SQLException {
+ if (userCache != null)
+ return;
+ synchronized (DbUser.class) {
+ if (userCache == null) {
+ try (MysqlConnection connection = Database.getConnection()) {
+ userCache = new TimeoutHashMap<>(TimeUnit.DAYS.toMillis(2));
+ MysqlStatement stmt = connection.prepareStatement("SELECT userid, firstname, lastname, email, organizationid"
+ + " FROM user ORDER BY lastlogin DESC LIMIT 30");
+ ResultSet rs = stmt.executeQuery();
+ while (rs.next()) {
+ UserInfo user = new UserInfo(rs.getString("userid"), rs.getString("firstname"),
+ rs.getString("lastname"), rs.getString("email"),
+ rs.getString("organizationid"));
+ userCache.put(user.userId, user);
+ }
+ } catch (SQLException e) {
+ LOGGER.error("Query failed in DbUser.initCache()", e);
+ throw e;
+ }
+ }
+ }
+ }
+
/**
* Get all users, starting at page <code>page</code>.
* This function will return a maximum of {@link #PER_PAGE} results, so
@@ -105,6 +135,36 @@ public class DbUser {
LOGGER.error("Query failed in DbUser.writeUserOnLogin()", e);
throw e;
}
+ synchronized (DbUser.class) {
+ initCache();
+ userCache.put(ui.userId, ui);
+ }
+ }
+
+ public static UserInfo getCached(String userId) throws SQLException, TNotFoundException {
+ synchronized (DbUser.class) {
+ initCache();
+ UserInfo user = userCache.get(userId);
+ if (user != null)
+ return user;
+ }
+ try (MysqlConnection connection = Database.getConnection()) {
+ MysqlStatement stmt = connection.prepareStatement("SELECT userid, firstname, lastname, email, organizationid"
+ + " FROM user WHERE userid = :userid");
+ stmt.setString("userid", userId);
+ ResultSet rs = stmt.executeQuery();
+ if (!rs.next())
+ throw new TNotFoundException();
+ UserInfo user = new UserInfo(rs.getString("userid"), rs.getString("firstname"),
+ rs.getString("lastname"), rs.getString("email"), rs.getString("organizationid"));
+ synchronized (DbUser.class) {
+ userCache.put(user.userId, user);
+ }
+ return user;
+ } catch (SQLException e) {
+ LOGGER.error("Query failed in DbUser.getCached()", e);
+ throw e;
+ }
}
}
diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/models/LocalImageVersion.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/models/LocalImageVersion.java
index 238633fd..fea36200 100644
--- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/models/LocalImageVersion.java
+++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/models/LocalImageVersion.java
@@ -13,7 +13,7 @@ public class LocalImageVersion {
public final boolean isValid;
public final long createTime;
-
+
public final long expireTime;
public LocalImageVersion(String imageVersionId, String imageBaseId, String filePath, long fileSize,
@@ -27,4 +27,10 @@ public class LocalImageVersion {
this.isValid = isValid;
}
+ @Override
+ public boolean equals(Object that) {
+ return this.imageVersionId != null && that != null && (that instanceof LocalImageVersion)
+ && this.imageVersionId.equals(((LocalImageVersion) that).imageVersionId);
+ }
+
}
diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/FileServer.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/FileServer.java
index 7d158459..6e8bcf1c 100644
--- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/FileServer.java
+++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/FileServer.java
@@ -18,8 +18,8 @@ import javax.net.ssl.SSLContext;
import org.apache.log4j.Logger;
import org.openslx.bwlp.sat.database.mappers.DbImage;
import org.openslx.bwlp.sat.database.models.LocalImageVersion;
-import org.openslx.bwlp.sat.util.Configuration;
import org.openslx.bwlp.sat.util.Constants;
+import org.openslx.bwlp.sat.util.FileSystem;
import org.openslx.bwlp.sat.util.Formatter;
import org.openslx.bwlp.sat.util.Identity;
import org.openslx.bwlp.thrift.iface.ImageDetailsRead;
@@ -190,25 +190,29 @@ public class FileServer implements IncomingEvent {
+ "/" + Constants.MAX_UPLOADS + ").");
}
// Determine src file and go
- File srcFile = composeAbsolutePath(localImageData);
- if (!srcFile.canRead()) {
+ File srcFile = FileSystem.composeAbsoluteImagePath(localImageData);
+ String errorMessage = null;
+ if (srcFile == null) {
+ LOGGER.warn("Rejecting download of VID " + localImageData.imageVersionId
+ + ": Invalid local relative path");
+ errorMessage = "File has invalid path on server";
+ } else if (!srcFile.canRead()) {
LOGGER.warn("Rejecting download of VID " + localImageData.imageVersionId + ": Missing "
+ srcFile.getPath());
- try {
- DbImage.markValid(false, true, localImageData);
- } catch (SQLException e) {
- }
- throw new TTransferRejectedException("File missing on server");
+ errorMessage = "File missing on server";
}
if (srcFile.length() != localImageData.fileSize) {
LOGGER.warn("Rejecting download of VID " + localImageData.imageVersionId + ": Size mismatch for "
+ srcFile.getPath() + " (expected " + localImageData.fileSize + ", is "
+ srcFile.length() + ")");
+ errorMessage = "File corrupted on server";
+ }
+ if (errorMessage != null) {
try {
DbImage.markValid(false, true, localImageData);
} catch (SQLException e) {
}
- throw new TTransferRejectedException("File corrupted on server");
+ throw new TTransferRejectedException(errorMessage);
}
String key = UUID.randomUUID().toString();
ActiveDownload transfer = new ActiveDownload(key, srcFile);
@@ -216,8 +220,4 @@ public class FileServer implements IncomingEvent {
return transfer;
}
- public static File composeAbsolutePath(LocalImageVersion localImageData) {
- return new File(Configuration.getVmStoreBasePath(), localImageData.filePath);
- }
-
}
diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/maintenance/DeleteOldImages.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/maintenance/DeleteOldImages.java
index 9a308dcd..c3168253 100644
--- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/maintenance/DeleteOldImages.java
+++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/maintenance/DeleteOldImages.java
@@ -8,7 +8,7 @@ import org.apache.log4j.Logger;
import org.joda.time.DateTime;
import org.openslx.bwlp.sat.database.mappers.DbImage;
import org.openslx.bwlp.sat.database.models.LocalImageVersion;
-import org.openslx.bwlp.sat.fileserv.FileServer;
+import org.openslx.bwlp.sat.util.FileSystem;
import org.openslx.util.QuickTimer;
import org.openslx.util.QuickTimer.Task;
@@ -73,7 +73,7 @@ public class DeleteOldImages implements Runnable {
for (LocalImageVersion version : versions) {
if (version.expireTime < hardDelete) {
hardDeleteCount++;
- FileServer.composeAbsolutePath(version).delete();
+ FileSystem.deleteImageRelatedFiles(version);
try {
DbImage.deletePermanently(version);
} catch (SQLException e) {
diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/maintenance/Mailer.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/maintenance/Mailer.java
new file mode 100644
index 00000000..d2241e37
--- /dev/null
+++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/maintenance/Mailer.java
@@ -0,0 +1,161 @@
+package org.openslx.bwlp.sat.maintenance;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.log4j.Logger;
+import org.openslx.bwlp.sat.database.mappers.DbImage;
+import org.openslx.bwlp.sat.database.mappers.DbImagePermissions;
+import org.openslx.bwlp.sat.database.mappers.DbLecturePermissions;
+import org.openslx.bwlp.sat.database.mappers.DbUser;
+import org.openslx.bwlp.sat.database.models.LocalImageVersion;
+import org.openslx.bwlp.sat.util.Formatter;
+import org.openslx.bwlp.thrift.iface.ImageDetailsRead;
+import org.openslx.bwlp.thrift.iface.ImagePermissions;
+import org.openslx.bwlp.thrift.iface.ImageVersionDetails;
+import org.openslx.bwlp.thrift.iface.LecturePermissions;
+import org.openslx.bwlp.thrift.iface.LectureSummary;
+import org.openslx.bwlp.thrift.iface.TNotFoundException;
+import org.openslx.bwlp.thrift.iface.UserInfo;
+
+public class Mailer {
+
+ private static final Logger LOGGER = Logger.getLogger(Mailer.class);
+
+ public static void lectureAutoUpdate(List<LectureSummary> lectures, LocalImageVersion newVersion) {
+ for (LectureSummary lecture : lectures) {
+ LOGGER.debug("Auto-Update mail for " + lecture.lectureId + " " + lecture.lectureName);
+ List<UserInfo> relevantUsers = getUserToMail(lecture);
+ for (UserInfo user : relevantUsers) {
+ LOGGER.debug("Sending auto-update info to " + Formatter.userFullName(user));
+ }
+ }
+ }
+
+ public static void lectureForcedUpdate(List<LectureSummary> lectures, LocalImageVersion newVersion) {
+ for (LectureSummary lecture : lectures) {
+ LOGGER.debug("Forced-Update mail for " + lecture.lectureId + " " + lecture.lectureName);
+ List<UserInfo> relevantUsers = getUserToMail(lecture);
+ for (UserInfo user : relevantUsers) {
+ LOGGER.debug("Sending forced-update info to " + Formatter.userFullName(user));
+ }
+ }
+ }
+
+ public static void lectureDeactivated(List<LectureSummary> lectures) {
+ for (LectureSummary lecture : lectures) {
+ LOGGER.debug("Deactivated mail for " + lecture.lectureId + " " + lecture.lectureName);
+ List<UserInfo> relevantUsers = getUserToMail(lecture);
+ for (UserInfo user : relevantUsers) {
+ LOGGER.debug("Sending deactivated info to " + Formatter.userFullName(user));
+ }
+ }
+ }
+
+ private static List<UserInfo> getUserToMail(LectureSummary lecture) {
+ Map<String, LecturePermissions> users;
+ try {
+ users = DbLecturePermissions.getForLecture(lecture.lectureId, false);
+ } catch (SQLException e) {
+ users = new HashMap<>();
+ }
+ users.put(lecture.ownerId, new LecturePermissions(true, true));
+ List<UserInfo> list = new ArrayList<>(users.size());
+ for (Entry<String, LecturePermissions> entry : users.entrySet()) {
+ LecturePermissions perms = entry.getValue();
+ if (!perms.admin && !perms.edit)
+ continue;
+ UserInfo user;
+ try {
+ user = DbUser.getCached(entry.getKey());
+ } catch (TNotFoundException e) {
+ LOGGER.warn("UserID " + entry.getKey() + " unknown");
+ continue;
+ } catch (SQLException e) {
+ continue; // Logging happened in DbUser
+ }
+ list.add(user);
+ }
+ return list;
+ }
+
+ private static List<UserInfo> getUserToMail(ImageDetailsRead image) {
+ Map<String, ImagePermissions> users;
+ try {
+ users = DbImagePermissions.getForImageBase(image.imageBaseId, false);
+ } catch (SQLException e) {
+ users = new HashMap<>();
+ }
+ users.put(image.ownerId, new ImagePermissions(true, true, true, true));
+ List<UserInfo> list = new ArrayList<>(users.size());
+ for (Entry<String, ImagePermissions> entry : users.entrySet()) {
+ ImagePermissions perms = entry.getValue();
+ if (!perms.admin && !perms.edit)
+ continue;
+ UserInfo user;
+ try {
+ user = DbUser.getCached(entry.getKey());
+ } catch (TNotFoundException e) {
+ LOGGER.warn("UserID " + entry.getKey() + " unknown");
+ continue;
+ } catch (SQLException e) {
+ continue; // Logging happened in DbUser
+ }
+ list.add(user);
+ }
+ return list;
+ }
+
+ public static void versionDeleted(String imageBaseId, LocalImageVersion oldLocal,
+ LocalImageVersion newLocal) {
+ ImageDetailsRead image;
+ try {
+ image = DbImage.getImageDetails(null, imageBaseId);
+ } catch (TNotFoundException | SQLException e) {
+ LOGGER.warn("Version Deleted for image=" + imageBaseId + " failed", e);
+ return;
+ }
+ LOGGER.debug("Sending info mail about deleted image version "
+ + (oldLocal == null ? "-" : oldLocal.imageVersionId) + " (" + image.imageName + ")");
+ ImageVersionDetails oldVersion = null;
+ ImageVersionDetails newVersion = null;
+ for (ImageVersionDetails version : image.versions) {
+ if (oldLocal != null && version.versionId.equals(oldLocal.imageVersionId)) {
+ oldVersion = version;
+ }
+ if (newLocal != null && version.versionId.equals(newLocal.imageVersionId)) {
+ newVersion = version;
+ }
+ }
+ String message;
+ if (newVersion == null) {
+ message = "Last version deleted; WARNING: No more valid image versions";
+ } else {
+ String uploaderName;
+ try {
+ UserInfo uploader = DbUser.getCached(newVersion.uploaderId);
+ uploaderName = uploader.firstName + " " + uploader.lastName + " <" + uploader.eMail + ">";
+ } catch (TNotFoundException | SQLException e) {
+ uploaderName = "an unknown person";
+ ;
+ }
+ if (oldVersion == null) {
+ message = "Old version deleted;";
+ } else {
+ message = "Old version (created " + Formatter.date(oldVersion.createTime) + ") deleted;";
+ }
+ message += " new version now in use was uploaded on "
+ + Formatter.date(newVersion.createTime) + " by " + uploaderName;
+ }
+ LOGGER.debug("Mail: " + message);
+ List<UserInfo> relevantUsers = getUserToMail(image);
+ for (UserInfo user : relevantUsers) {
+ LOGGER.debug("Sending mail to " + Formatter.userFullName(user));
+ }
+ }
+
+}
diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/Configuration.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/Configuration.java
index 07cd3a8d..b02d73d0 100644
--- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/Configuration.java
+++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/Configuration.java
@@ -32,7 +32,7 @@ public class Configuration {
}
vmStoreBasePath = new File(prop.getProperty("vmstore.path"));
- vmStoreProdPath = new File(vmStoreBasePath, "prod");
+ vmStoreProdPath = new File(vmStoreBasePath, "bwlehrpool_store");
dbUri = prop.getProperty("db.uri");
dbUsername = prop.getProperty("db.username");
dbPassword = prop.getProperty("db.password");
@@ -43,6 +43,12 @@ public class Configuration {
// Static ("real") fields
+ /**
+ * Get the path of the VM storage space. Traditionally, this is
+ * '/srv/openslx/nfs'.
+ *
+ * @return path of the VM storage
+ */
public static File getVmStoreBasePath() {
return vmStoreBasePath;
}
diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/FileSystem.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/FileSystem.java
index c8f24e3f..a8a7151b 100644
--- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/FileSystem.java
+++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/FileSystem.java
@@ -2,7 +2,9 @@ package org.openslx.bwlp.sat.util;
import java.io.File;
+import org.apache.commons.io.FilenameUtils;
import org.apache.log4j.Logger;
+import org.openslx.bwlp.sat.database.models.LocalImageVersion;
import org.openslx.util.QuickTimer;
import org.openslx.util.QuickTimer.Task;
@@ -26,24 +28,106 @@ public class FileSystem {
}
/**
- * Delete given file on the quicktimer thread, preventing the calling thread
- * from freezing/hanging on I/O problems.
+ * Delete given file on the {@link QuickTimer} thread, preventing the
+ * calling thread from freezing/hanging on I/O problems.
*
* @param file the file to delete
*/
- public static void deleteAsync(final File file) {
+ public static void deleteAsync(final File... files) {
+ if (files == null || files.length == 0)
+ return;
QuickTimer.scheduleOnce(new Task() {
@Override
public void fire() {
- if (!file.exists()) {
- LOGGER.info("deleteAsync called for nonexistent file " + file.getAbsolutePath());
- return;
- }
- if (!file.delete()) {
- LOGGER.warn("Could not delete file " + file.getAbsolutePath());
+ for (File file : files) {
+ if (!file.exists()) {
+ LOGGER.info("deleteAsync called for nonexistent file " + file.getAbsolutePath());
+ return;
+ }
+ if (!file.delete()) {
+ LOGGER.warn("Could not delete file " + file.getAbsolutePath());
+ }
}
}
});
}
+ /**
+ * Checks if the VM store is mounted.
+ *
+ * @return true if mounted, false otherwise.
+ */
+ public static boolean isStorageMounted() {
+ File flagFile = new File(Configuration.getVmStoreBasePath(), ".notmounted");
+ if (flagFile.exists())
+ return false;
+ File prodPath = Configuration.getVmStoreProdPath();
+ if (prodPath.isDirectory())
+ return true;
+ return prodPath.mkdir();
+ }
+
+ private static Object storageMutex = new Object();
+
+ public static boolean waitForStorage() {
+ if (isStorageMounted())
+ return true;
+ synchronized (storageMutex) {
+ if (isStorageMounted())
+ return true;
+ LOGGER.warn("VM storage gone, waiting for it to reappear...");
+ int intrCount = 0;
+ do {
+ try {
+ Thread.sleep(10000);
+ intrCount = 0;
+ } catch (InterruptedException e) {
+ intrCount++;
+ if (intrCount > 2) {
+ LOGGER.warn("Lost patience while waiting");
+ return false;
+ }
+ }
+ } while (!isStorageMounted());
+ }
+ LOGGER.info("VM storage back online");
+ return true;
+ }
+
+ /**
+ * Delete the given image file (and all related files) from the storage.
+ *
+ * @param image The image to be deleted
+ */
+ public static void deleteImageRelatedFiles(LocalImageVersion image) {
+ File imageFile = composeAbsoluteImagePath(image);
+ if (imageFile == null)
+ return;
+ File metaFile = new File(imageFile.getPath() + ".meta");
+ File crcFile = new File(imageFile.getPath() + ".crc");
+ File mapFile = new File(imageFile.getPath() + ".map");
+ if (!waitForStorage())
+ return;
+ deleteAsync(imageFile, metaFile, crcFile, mapFile);
+ }
+
+ /**
+ * Given a local image version, return a {@link File} object that holds the
+ * full path to the image file in the file system.
+ *
+ * @param localImageData
+ * @return {@link File} representing the physical image file, or
+ * <code>null</code> if the path would not be valid
+ */
+ public static File composeAbsoluteImagePath(LocalImageVersion localImageData) {
+ String rel = localImageData.filePath == null ? null
+ : FilenameUtils.normalize(localImageData.filePath);
+ if (rel == null || rel.startsWith("/")) {
+ LOGGER.warn("Invalid path for local image " + localImageData.imageVersionId + ": "
+ + localImageData.filePath);
+ return null;
+ }
+ return new File(Configuration.getVmStoreBasePath(), rel);
+ }
+
}
diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/Formatter.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/Formatter.java
index bc64c8fe..adfeaac3 100644
--- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/Formatter.java
+++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/Formatter.java
@@ -10,7 +10,9 @@ import org.openslx.bwlp.thrift.iface.UserInfo;
public class Formatter {
private static final DateTimeFormatter vmNameDateFormat = DateTimeFormat.forPattern("dd_HH-mm-ss");
-
+
+ private static final DateTimeFormatter displayDateFormat = DateTimeFormat.forPattern("dd.MM.yy HH:mm");
+
/**
* Generate a unique file name used for a virtual machine
* image that is currently uploading.
@@ -54,4 +56,16 @@ public class Formatter {
public static String userFullName(UserInfo ui) {
return ui.firstName + " " + ui.lastName;
}
+
+ /**
+ * Format a unix time stamp as dd.MM.yy HH:mm
+ *
+ * @param unixTime seconds since 01.01.1970 UTC
+ * @return dd.MM.yy HH:mm
+ */
+ public static String date(long unixTime) {
+ if (unixTime == 0)
+ return "???";
+ return displayDateFormat.print(unixTime * 1000);
+ }
}