diff options
author | Simon Rettberg | 2015-08-24 18:33:53 +0200 |
---|---|---|
committer | Simon Rettberg | 2015-08-24 18:33:53 +0200 |
commit | ff5cf4a6e6e7fd59e79ff097264644fc1605c464 (patch) | |
tree | 86feece32cea13507fbd20f904e6988ff4ab92d1 | |
parent | [client] Major layout fix (diff) | |
download | tutor-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)
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); + } } |