summaryrefslogtreecommitdiffstats
path: root/dozentenmodulserver/src/main/java
diff options
context:
space:
mode:
authorSimon Rettberg2015-08-21 20:26:09 +0200
committerSimon Rettberg2015-08-21 20:26:09 +0200
commit1f8d2edf172c35b2396e568383d9e461c058a1fa (patch)
tree90662568b358a6c06df5533fbf67a236ed85fda3 /dozentenmodulserver/src/main/java
parent[client] tried to fix teh broken (diff)
downloadtutor-module-1f8d2edf172c35b2396e568383d9e461c058a1fa.tar.gz
tutor-module-1f8d2edf172c35b2396e568383d9e461c058a1fa.tar.xz
tutor-module-1f8d2edf172c35b2396e568383d9e461c058a1fa.zip
[server] Implement missing lecture thrift calls, clean up transfer handling a bit
Diffstat (limited to 'dozentenmodulserver/src/main/java')
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbImage.java62
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbLecture.java16
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbLecturePermissions.java55
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbUser.java3
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/models/LocalImageVersion.java11
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/AbstractTransfer.java92
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/ActiveDownload.java56
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/ActiveUpload.java111
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/FileServer.java42
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/permissions/User.java41
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/thrift/ServerHandler.java64
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/util/FileSystem.java24
12 files changed, 444 insertions, 133 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 a6b6d4b8..5a002e76 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
@@ -1,5 +1,6 @@
package org.openslx.bwlp.sat.database.mappers;
+import java.io.File;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
@@ -12,6 +13,7 @@ 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.permissions.User;
import org.openslx.bwlp.thrift.iface.ImageBaseWrite;
import org.openslx.bwlp.thrift.iface.ImageDetailsRead;
@@ -108,20 +110,36 @@ public class DbImage {
public static LocalImageVersion getLocalImageData(String imageVersionId) throws TNotFoundException,
SQLException {
try (MysqlConnection connection = Database.getConnection()) {
- MysqlStatement stmt = connection.prepareStatement("SELECT imageversionid, imagebaseid, filepath, filesize"
- + " FROM imageversion WHERE imageversionid = :versionid LIMIT 1");
- stmt.setString("versionid", imageVersionId);
+ MysqlStatement stmt = connection.prepareStatement("SELECT imageversionid, imagebaseid, filepath, filesize, createtime, isvalid"
+ + " FROM imageversion WHERE imageversionid = :imageversionid");
+ stmt.setString("imageversionid", imageVersionId);
ResultSet rs = stmt.executeQuery();
if (!rs.next())
throw new TNotFoundException();
return new LocalImageVersion(rs.getString("imageversionid"), rs.getString("imagebaseid"),
- rs.getString("filepath"), rs.getLong("filesize"));
+ rs.getString("filepath"), rs.getLong("filesize"), rs.getLong("createtime"),
+ rs.getBoolean("isvalid"));
} 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("SELECT imageversionid, imagebaseid, filepath, filesize, createtime, isvalid"
+ + " FROM imageversion WHERE imagebaseid = :imagebaseid");
+ stmt.setString("imagebaseid", imageBaseId);
+ ResultSet rs = stmt.executeQuery();
+ 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.getBoolean("isvalid")));
+ }
+ return list;
+ }
+
/**
* Private helper to create an {@link ImageSummaryRead} instance from a
* {@link ResultSet}
@@ -456,13 +474,18 @@ public class DbImage {
}
}
+ public static void markValid(MysqlConnection connection, LocalImageVersion imageVersion, boolean valid)
+ throws SQLException {
+ MysqlStatement stmt = connection.prepareStatement("UPDATE imageversion SET isvalid = :valid"
+ + " WHERE imageversionid = :imageversionid");
+ stmt.setString("imageversionid", imageVersion.imageVersionId);
+ stmt.setBoolean("valid", valid);
+ stmt.executeUpdate();
+ }
+
public static void markValid(LocalImageVersion imageVersion, boolean valid) throws SQLException {
try (MysqlConnection connection = Database.getConnection()) {
- MysqlStatement stmt = connection.prepareStatement("UPDATE imageversion SET isvalid = :valid"
- + " WHERE imageversionid = :imageversionid");
- stmt.setString("imageversionid", imageVersion.imageVersionId);
- stmt.setBoolean("valid", valid);
- stmt.executeUpdate();
+ markValid(connection, imageVersion, valid);
connection.commit();
} catch (SQLException e) {
LOGGER.error("Query failed in DbImage.markInvalid()", e);
@@ -505,15 +528,20 @@ public class DbImage {
throw new TNotFoundException();
}
// Determine new latest version, as we might have to update the imagebase and lecture tables
- List<ImageVersionDetails> versions = DbImage.getImageVersions(connection, imageBaseId);
- ImageVersionDetails newVersion = null;
- ImageVersionDetails oldVersion = null;
- for (ImageVersionDetails version : versions) {
- if (version.versionId.equals(changingImageVersionId)) {
+ List<LocalImageVersion> versions = DbImage.getLocalImageVersions(connection, imageBaseId);
+ LocalImageVersion newVersion = null;
+ LocalImageVersion oldVersion = null;
+ for (LocalImageVersion version : versions) {
+ if (version.imageVersionId.equals(changingImageVersionId)) {
oldVersion = version;
}
if (version.isValid && (newVersion == null || version.createTime > newVersion.createTime)) {
- newVersion = version;
+ File versionFile = FileServer.composeAbsolutePath(version);
+ if (versionFile.canRead() && versionFile.length() == version.fileSize) {
+ newVersion = version;
+ } else {
+ markValid(connection, version, false);
+ }
}
}
if (oldVersion == null) {
@@ -521,7 +549,7 @@ public class DbImage {
} else {
// Switch any lectures linking to this version if applicable
if (oldVersion.isValid) {
- DbLecture.autoUpdateUsedImage(connection, imageBaseId, newVersion.versionId);
+ DbLecture.autoUpdateUsedImage(connection, imageBaseId, newVersion.imageVersionId);
} else {
DbLecture.forcefullySwitchUsedImage(connection, oldVersion, newVersion);
}
@@ -529,7 +557,7 @@ public class DbImage {
// Now update the latestversionid of the baseimage if applicable
MysqlStatement latestStmt = connection.prepareStatement("UPDATE imagebase SET latestversionid = :newversionid"
+ " WHERE imagebaseid = :imagebaseid");
- latestStmt.setString("newversionid", newVersion == null ? null : newVersion.versionId);
+ latestStmt.setString("newversionid", newVersion == null ? null : newVersion.imageVersionId);
latestStmt.setString("imagebaseid", imageBaseId);
latestStmt.executeUpdate();
}
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 580c19c9..bfd1513a 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
@@ -11,9 +11,9 @@ import org.apache.log4j.Logger;
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.permissions.User;
import org.openslx.bwlp.sat.util.Json;
-import org.openslx.bwlp.thrift.iface.ImageVersionDetails;
import org.openslx.bwlp.thrift.iface.LectureRead;
import org.openslx.bwlp.thrift.iface.LectureSummary;
import org.openslx.bwlp.thrift.iface.LectureWrite;
@@ -138,7 +138,7 @@ public class DbLecture {
+ " 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)"
- + " WHERE lectureid = :lectureid");
+ + " WHERE l.lectureid = :lectureid");
stmt.setString("lectureid", lectureId);
stmt.setString("userid", user.userId);
ResultSet rs = stmt.executeQuery();
@@ -336,13 +336,13 @@ public class DbLecture {
* Called when an image version is deleted or marked for deletion, so that
* linking lectures switch over to other available versions.
*/
- protected static void forcefullySwitchUsedImage(MysqlConnection connection, ImageVersionDetails oldVersion,
- ImageVersionDetails newVersion) throws TNotFoundException, SQLException {
+ protected static void forcefullySwitchUsedImage(MysqlConnection connection, LocalImageVersion oldVersion,
+ LocalImageVersion newVersion) throws TNotFoundException, SQLException {
if (oldVersion == newVersion
- || (newVersion != null && newVersion.versionId.equals(oldVersion.versionId)))
+ || (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.versionId);
+ List<String> lectures = getAllUsingImageVersion(connection, oldVersion.imageVersionId);
if (lectures.isEmpty())
return;
// TODO: If there is no new candidate to switch to, send a warning via mail
@@ -353,8 +353,8 @@ public class DbLecture {
// Update and send info mail
MysqlStatement stmt = connection.prepareStatement("UPDATE lecture SET imageversionid = :newversionid"
+ " WHERE imageversionid = :oldversionid");
- stmt.setString("oldversionid", oldVersion.versionId);
- stmt.setString("newversionid", newVersion.versionId);
+ 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/DbLecturePermissions.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbLecturePermissions.java
index e8bd8a07..77c7ea0d 100644
--- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbLecturePermissions.java
+++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbLecturePermissions.java
@@ -2,11 +2,19 @@ package org.openslx.bwlp.sat.database.mappers;
import java.sql.ResultSet;
import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.log4j.Logger;
+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.thrift.iface.LecturePermissions;
public class DbLecturePermissions {
+ private static final Logger LOGGER = Logger.getLogger(DbLecturePermissions.class);
+
/**
* Build an instance of {@link LecturePermissions} by reading the given
* columns from the given {@link ResultSet}. If there are no permissions
@@ -60,4 +68,51 @@ public class DbLecturePermissions {
return fromResultSet(rs, "caneditdefault", "canadmindefault");
}
+ public static void writeForLecture(String lectureId, Map<String, LecturePermissions> permissions)
+ throws SQLException {
+ try (MysqlConnection connection = Database.getConnection()) {
+ MysqlStatement stmt = connection.prepareStatement("DELETE FROM lecturepermission"
+ + " WHERE lectureid = :lectureid");
+ stmt.setString("lectureid", lectureId);
+ stmt.executeUpdate();
+ stmt = connection.prepareStatement("INSERT INTO lecturepermission"
+ + " (lectureid, userid, canedit, canadmin)"
+ + " VALUES (:lectureid, :userid, :canedit, :canadmin)");
+ stmt.setString("lectureid", lectureId);
+ for (Map.Entry<String, LecturePermissions> entry : permissions.entrySet()) {
+ LecturePermissions perm = entry.getValue();
+ stmt.setString("userid", entry.getKey());
+ stmt.setBoolean("canedit", perm.edit);
+ stmt.setBoolean("canadmin", perm.admin);
+ stmt.executeUpdate();
+ }
+ connection.commit();
+ } catch (SQLException e) {
+ LOGGER.error("Query failed in DbLecturePermissions.writeForLecture()", e);
+ throw e;
+ }
+ }
+
+ public static Map<String, LecturePermissions> getForLecture(String lectureId, boolean adminOnly)
+ throws SQLException {
+ try (MysqlConnection connection = Database.getConnection()) {
+ MysqlStatement stmt = connection.prepareStatement("SELECT userid, canedit, canadmin"
+ + " FROM lecturepermission WHERE lectureid = :lectureid");
+ stmt.setString("lectureid", lectureId);
+ ResultSet rs = stmt.executeQuery();
+ Map<String, LecturePermissions> list = new HashMap<>();
+ while (rs.next()) {
+ boolean admin = rs.getBoolean("canadmin");
+ if (adminOnly && !admin)
+ continue;
+ LecturePermissions perm = new LecturePermissions(rs.getBoolean("canedit"), admin);
+ list.put(rs.getString("userid"), perm);
+ }
+ return list;
+ } catch (SQLException e) {
+ LOGGER.error("Query failed in DbImagePermissions.getForImageBase()", e);
+ throw e;
+ }
+ }
+
}
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 886f08ec..9c80caf4 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
@@ -92,7 +92,8 @@ public class DbUser {
+ " (userid, firstname, lastname, email, organizationid, lastlogin, canlogin, issuperuser)"
+ " VALUES"
+ " (:userid, :firstname, :lastname, :email, :organizationid, UNIX_TIMESTAMP(), 1, 0)"
- + " ON DUPLICATE KEY UPDATE lastlogin = UNIX_TIMESTAMP()");
+ + " ON DUPLICATE KEY UPDATE lastlogin = UNIX_TIMESTAMP(), email = VALUES(email),"
+ + " firstname = VALUES(firstname), lastname = VALUES(lastname)");
stmt.setString("userid", ui.userId);
stmt.setString("firstname", ui.firstName);
stmt.setString("lastname", ui.lastName);
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 91c5acac..875c46f3 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
@@ -9,12 +9,19 @@ public class LocalImageVersion {
public final String filePath;
public final long fileSize;
-
- public LocalImageVersion(String imageVersionId, String imageBaseId, String filePath, long fileSize) {
+
+ public final boolean isValid;
+
+ public final long createTime;
+
+ public LocalImageVersion(String imageVersionId, String imageBaseId, String filePath, long fileSize,
+ long createTime, boolean isValid) {
this.imageVersionId = imageVersionId;
this.imageBaseId = imageBaseId;
this.filePath = filePath;
this.fileSize = fileSize;
+ this.createTime = createTime;
+ this.isValid = isValid;
}
}
diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/AbstractTransfer.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/AbstractTransfer.java
new file mode 100644
index 00000000..bef4f8fd
--- /dev/null
+++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/AbstractTransfer.java
@@ -0,0 +1,92 @@
+package org.openslx.bwlp.sat.fileserv;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+public abstract class AbstractTransfer {
+
+ /**
+ * How long to keep this transfer information when the transfer is
+ * (potentially) done
+ */
+ private static final long FINISH_TIMEOUT = TimeUnit.MINUTES.toMillis(5);
+
+ /**
+ * How long to keep this transfer information when there are no active
+ * connections and the transfer seems unfinished
+ */
+ private static final long IDLE_TIMEOUT = TimeUnit.HOURS.toMillis(4);
+
+ /**
+ * Time stamp of when (we think) the transfer finished. Clients can/might
+ * not tell us they're done, and simply taking "no active connection" as a
+ * sign the download is done might have unwanted effects if the user's
+ * connection drops for a minute. If this time stamp (plus a FINISH_TIMEOUT)
+ * passed,
+ * we consider the download done and flag it for removal.
+ * If set to zero, the transfer is not finished, or not assumed to have
+ * finished.
+ */
+ protected final AtomicLong potentialFinishTime = new AtomicLong(0);
+
+ /**
+ * Time of last activity on this transfer.
+ */
+ protected final AtomicLong lastActivityTime = new AtomicLong(System.currentTimeMillis());
+
+ private final String transferId;
+
+ public AbstractTransfer(String transferId) {
+ this.transferId = transferId;
+ }
+
+ /**
+ * Returns true if the transfer is considered completed.
+ *
+ * @param now pass System.currentTimeMillis()
+ * @return true if the transfer is considered completed
+ */
+ public boolean isComplete(long now) {
+ long val = potentialFinishTime.get();
+ return val != 0 && val + FINISH_TIMEOUT < now;
+ }
+
+ /**
+ * Returns true if there has been no activity on this transfer for a certain
+ * amount of time.
+ *
+ * @param now pass System.currentTimeMillis()
+ * @return true if the transfer reached its idle timeout
+ */
+ public final boolean hasReachedIdleTimeout(long now) {
+ return getActiveConnectionCount() == 0 && lastActivityTime.get() + IDLE_TIMEOUT < now;
+ }
+
+ public final String getId() {
+ return transferId;
+ }
+
+ /**
+ * Returns true if this transfer would potentially accept new connections.
+ * This should NOT return false if there are too many concurrent
+ * connections, as this is used to signal the client whether to keep trying
+ * to connect.
+ *
+ * @return true if this transfer would potentially accept new connections
+ */
+ public abstract boolean isActive();
+
+ /**
+ * Cancel this transfer, aborting all active connections and rejecting
+ * further incoming ones.
+ */
+ public abstract void cancel();
+
+ /**
+ * Returns number of active transfer connections.
+ *
+ * @return number of active transfer connections
+ */
+ public abstract int getActiveConnectionCount();
+
+}
diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/ActiveDownload.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/ActiveDownload.java
index 7180fe3d..504317f3 100644
--- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/ActiveDownload.java
+++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/ActiveDownload.java
@@ -7,43 +7,29 @@ import java.util.concurrent.ThreadPoolExecutor;
import org.apache.log4j.Logger;
import org.openslx.bwlp.sat.util.Constants;
-import org.openslx.bwlp.thrift.iface.TransferState;
import org.openslx.filetransfer.Uploader;
-public class ActiveDownload {
+public class ActiveDownload extends AbstractTransfer {
private static final Logger LOGGER = Logger.getLogger(ActiveDownload.class);
/**
- * How many concurrent connections per upload
+ * How many concurrent connections per download
*/
private static final int MAX_CONNECTIONS = Math.max(Constants.MAX_DOWNLOADS / 4, 1);
/**
- * Self reference for inner classes.
- */
- private final ActiveDownload activeDownload = this;
-
- /**
* This is a download, so we have uploaders
*/
private List<Uploader> uploads = new ArrayList<>();
private final File sourceFile;
- private final long fileSize;
-
- private final String transferId;
-
- /**
- * TransferState of this upload
- */
- private TransferState state = TransferState.IDLE;
+ private boolean isCanceled = false;
public ActiveDownload(String uuid, File file) {
- this.transferId = uuid;
+ super(uuid);
this.sourceFile = file;
- this.fileSize = file.length();
}
/**
@@ -55,6 +41,9 @@ public class ActiveDownload {
* discarded
*/
public synchronized boolean addConnection(final Uploader connection, ThreadPoolExecutor pool) {
+ if (isCanceled)
+ return false;
+ potentialFinishTime.set(0);
synchronized (uploads) {
if (uploads.size() > MAX_CONNECTIONS)
return false;
@@ -64,7 +53,15 @@ public class ActiveDownload {
pool.execute(new Runnable() {
@Override
public void run() {
- connection.upload(sourceFile.getAbsolutePath());
+ potentialFinishTime.set(0);
+ boolean ret = connection.upload(sourceFile.getAbsolutePath());
+ synchronized (uploads) {
+ uploads.remove(connection);
+ }
+ if (ret && uploads.isEmpty()) {
+ potentialFinishTime.set(System.currentTimeMillis());
+ }
+ lastActivityTime.set(System.currentTimeMillis());
}
});
} catch (Exception e) {
@@ -74,13 +71,24 @@ public class ActiveDownload {
return true;
}
- public boolean isComplete() {
- // TODO Auto-generated method stub
- return false;
+ @Override
+ public synchronized void cancel() {
+ isCanceled = true;
+ synchronized (uploads) {
+ for (Uploader u : uploads) {
+ u.cancel();
+ }
+ }
+ }
+
+ @Override
+ public boolean isActive() {
+ return !isCanceled;
}
- public String getId() {
- return transferId;
+ @Override
+ public int getActiveConnectionCount() {
+ return uploads.size();
}
}
diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/ActiveUpload.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/ActiveUpload.java
index b3bf4133..16f85fc1 100644
--- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/ActiveUpload.java
+++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/fileserv/ActiveUpload.java
@@ -33,7 +33,7 @@ import org.openslx.filetransfer.util.HashChecker;
import org.openslx.filetransfer.util.HashChecker.HashCheckCallback;
import org.openslx.filetransfer.util.HashChecker.HashResult;
-public class ActiveUpload implements HashCheckCallback {
+public class ActiveUpload extends AbstractTransfer implements HashCheckCallback {
private static final Logger LOGGER = Logger.getLogger(ActiveUpload.class);
@@ -52,9 +52,9 @@ public class ActiveUpload implements HashCheckCallback {
*/
private List<Downloader> downloads = new ArrayList<>();
- private final File destinationFile;
+ private final File tmpFileName;
- private final RandomAccessFile outFile;
+ private final RandomAccessFile tmpFileHandle;
private final ChunkList chunks;
@@ -81,11 +81,6 @@ public class ActiveUpload implements HashCheckCallback {
private TransferState state = TransferState.IDLE;
/**
- * ID of this upload - will become this version id on success.
- */
- private final String uploadId;
-
- /**
* Description of this VM - binary dump of e.g. the *.vmx file (VMware)
*/
private final byte[] machineDescription;
@@ -115,13 +110,13 @@ public class ActiveUpload implements HashCheckCallback {
public ActiveUpload(String uploadId, UserInfo owner, ImageDetailsRead image, File destinationFile,
long fileSize, List<byte[]> sha1Sums, byte[] machineDescription) throws FileNotFoundException {
- this.destinationFile = destinationFile;
- this.outFile = new RandomAccessFile(destinationFile, "rw");
+ super(uploadId);
+ this.tmpFileName = destinationFile;
+ this.tmpFileHandle = new RandomAccessFile(destinationFile, "rw");
this.chunks = new ChunkList(fileSize, hashChecker == null ? null : sha1Sums);
this.owner = owner;
this.image = image;
this.fileSize = fileSize;
- this.uploadId = uploadId;
this.machineDescription = machineDescription;
}
@@ -160,7 +155,6 @@ public class ActiveUpload implements HashCheckCallback {
if (downloads.size() >= MAX_CONNECTIONS)
return false;
downloads.add(connection);
- // TODO: Remove when finished or executor rejects...
}
try {
pool.execute(new Runnable() {
@@ -173,14 +167,20 @@ public class ActiveUpload implements HashCheckCallback {
// the queue, so it will be handled again later...
chunks.markFailed(cbh.currentChunk);
}
- LOGGER.warn("Download of " + destinationFile.getAbsolutePath() + " failed");
+ LOGGER.warn("Download of " + tmpFileName.getAbsolutePath() + " failed");
+ }
+ lastActivityTime.set(System.currentTimeMillis());
+ synchronized (downloads) {
+ downloads.remove(connection);
}
if (chunks.isComplete()) {
finishUpload();
}
}
});
- state = TransferState.WORKING;
+ if (state == TransferState.IDLE) {
+ state = TransferState.WORKING;
+ }
} catch (Exception e) {
LOGGER.warn("threadpool rejected the incoming file transfer", e);
return false;
@@ -189,8 +189,8 @@ public class ActiveUpload implements HashCheckCallback {
}
/**
- * Write some data to the local file. Thread safe so we could
- * have multiple concurrent connections later.
+ * Write some data to the local file. Thread safe so we can
+ * have multiple concurrent connections.
*
* @param fileOffset
* @param dataLength
@@ -200,14 +200,15 @@ public class ActiveUpload implements HashCheckCallback {
private void writeFileData(long fileOffset, int dataLength, byte[] data) {
if (state != TransferState.WORKING)
throw new IllegalStateException("Cannot write to file if state != RUNNING");
- synchronized (outFile) {
+ synchronized (tmpFileHandle) {
try {
- outFile.seek(fileOffset);
- outFile.write(data, 0, dataLength);
+ tmpFileHandle.seek(fileOffset);
+ tmpFileHandle.write(data, 0, dataLength);
} catch (IOException e) {
- LOGGER.error("Cannot write to '" + destinationFile
+ LOGGER.error("Cannot write to '" + tmpFileName
+ "'. Disk full, network storage error, bad permissions, ...?", e);
fileWritable = false;
+ state = TransferState.ERROR;
}
}
}
@@ -216,22 +217,26 @@ public class ActiveUpload implements HashCheckCallback {
* Called when the upload finished.
*/
private synchronized void finishUpload() {
- synchronized (outFile) {
+ synchronized (tmpFileHandle) {
if (state != TransferState.WORKING)
return;
- Util.safeClose(outFile);
+ Util.safeClose(tmpFileHandle);
state = TransferState.FINISHED;
}
+ potentialFinishTime.set(System.currentTimeMillis());
+ // If owner is not set, this was a repair-transfer, which downloads directly to the existing target file.
+ // Nothing more to do in that case.
+ if (isRepairUpload())
+ return;
LOGGER.info("Finalizing uploaded image " + image.imageName);
- File file = destinationFile;
// Ready to go. First step: Rename temp file to something usable
- File destination = new File(file.getParent(), Formatter.vmName(owner, image.imageName));
+ File destination = new File(tmpFileName.getParent(), Formatter.vmName(owner, image.imageName));
// Sanity check: destination should be a sub directory of the vmStorePath
String relPath = FileSystem.getRelativePath(destination, Configuration.getVmStoreBasePath());
if (relPath == null) {
LOGGER.warn(destination.getAbsolutePath() + " is not a subdir of "
+ Configuration.getVmStoreBasePath().getAbsolutePath());
- state = TransferState.ERROR;
+ cancel();
return;
}
if (relPath.length() > 200) {
@@ -242,7 +247,7 @@ public class ActiveUpload implements HashCheckCallback {
boolean ret = false;
Exception renameException = null;
try {
- ret = file.renameTo(destination);
+ ret = tmpFileName.renameTo(destination);
} catch (Exception e) {
ret = false;
renameException = e;
@@ -250,35 +255,46 @@ public class ActiveUpload implements HashCheckCallback {
if (!ret) {
// Rename failed :-(
LOGGER.warn(
- "Could not rename '" + file.getAbsolutePath() + "' to '" + destination.getAbsolutePath()
- + "'", renameException);
- state = TransferState.ERROR;
+ "Could not rename '" + tmpFileName.getAbsolutePath() + "' to '"
+ + destination.getAbsolutePath() + "'", renameException);
+ cancel();
return;
}
// Now insert meta data into DB
try {
synchronized (versionWrittenToDb) {
- DbImage.createImageVersion(image.imageBaseId, uploadId, owner, fileSize, relPath,
+ DbImage.createImageVersion(image.imageBaseId, getId(), owner, fileSize, relPath,
versionSettings, chunks, machineDescription);
versionWrittenToDb.set(true);
}
} catch (SQLException e) {
LOGGER.error("Error finishing upload: Inserting version to DB failed", e);
state = TransferState.ERROR;
+ // Also delete uploaded file, as there is no refence to it
+ FileSystem.deleteAsync(destination);
+ cancel();
return;
}
}
+ @Override
public synchronized void cancel() {
+ if (state != TransferState.FINISHED) {
+ state = TransferState.ERROR;
+ if (!isRepairUpload() && tmpFileName.exists()) {
+ FileSystem.deleteAsync(tmpFileName);
+ }
+ }
synchronized (downloads) {
for (Downloader download : downloads) {
download.cancel();
}
}
- if (state != TransferState.FINISHED) {
- state = TransferState.ERROR;
- }
+ }
+
+ public boolean isRepairUpload() {
+ return owner == null;
}
/**
@@ -290,12 +306,8 @@ public class ActiveUpload implements HashCheckCallback {
return this.owner;
}
- public synchronized boolean isComplete() {
- return state == TransferState.FINISHED;
- }
-
public File getDestinationFile() {
- return this.destinationFile;
+ return this.tmpFileName;
}
public long getSize() {
@@ -346,7 +358,6 @@ public class ActiveUpload implements HashCheckCallback {
Thread.currentThread().interrupt();
return null;
}
- LOGGER.info("Next missing chunk: " + currentChunk);
if (currentChunk == null) {
return null; // No more chunks, returning null tells the Downloader we're done.
}
@@ -358,10 +369,6 @@ public class ActiveUpload implements HashCheckCallback {
return new TransferStatus(chunks.getStatusArray(), state);
}
- public String getId() {
- return uploadId;
- }
-
@Override
public void hashCheckDone(HashResult result, byte[] data, FileChunk chunk) {
switch (result) {
@@ -385,18 +392,18 @@ public class ActiveUpload implements HashCheckCallback {
}
private byte[] loadChunkFromFile(FileChunk chunk) {
- synchronized (outFile) {
+ synchronized (tmpFileHandle) {
if (state != TransferState.IDLE && state != TransferState.WORKING)
return null;
try {
- outFile.seek(chunk.range.startOffset);
+ tmpFileHandle.seek(chunk.range.startOffset);
byte[] buffer = new byte[chunk.range.getLength()];
- outFile.readFully(buffer);
+ tmpFileHandle.readFully(buffer);
return buffer;
} catch (IOException e) {
LOGGER.error(
"Could not read chunk " + chunk.getChunkIndex() + " of File "
- + destinationFile.toString(), e);
+ + tmpFileName.toString(), e);
return null;
}
}
@@ -425,6 +432,14 @@ public class ActiveUpload implements HashCheckCallback {
}
}
- // TODO: Clean up old stale uploads
+ @Override
+ public boolean isActive() {
+ return state == TransferState.IDLE || state == TransferState.WORKING;
+ }
+
+ @Override
+ public int getActiveConnectionCount() {
+ return downloads.size();
+ }
}
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 a71b7b3f..bfa5699f 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
@@ -115,22 +115,30 @@ public class FileServer implements IncomingEvent {
return uploads.get(uploadToken);
}
+ public ActiveDownload getDownloadByToken(String downloadToken) {
+ if (downloadToken == null)
+ return null;
+ return downloads.get(downloadToken);
+ }
+
public ActiveUpload createNewUserUpload(UserInfo owner, ImageDetailsRead image, long fileSize,
List<byte[]> sha1Sums, byte[] machineDescription) throws TTransferRejectedException {
Iterator<ActiveUpload> it = uploads.values().iterator();
+ final long now = System.currentTimeMillis();
int activeUploads = 0;
while (it.hasNext()) {
ActiveUpload upload = it.next();
- if (upload.isComplete()) {
- // TODO: Check age (short timeout) and remove
+ if (upload.isComplete(now) || upload.hasReachedIdleTimeout(now)) {
+ upload.cancel();
+ it.remove();
continue;
- } else {
- // Check age (long timeout) and remove
}
activeUploads++;
}
- if (activeUploads > Constants.MAX_UPLOADS)
- throw new TTransferRejectedException("Server busy. Too many running uploads.");
+ if (activeUploads > Constants.MAX_UPLOADS) {
+ throw new TTransferRejectedException("Server busy. Too many running uploads (" + activeUploads
+ + "/" + Constants.MAX_UPLOADS + ").");
+ }
File destinationFile = null;
do {
destinationFile = Formatter.getTempImageName();
@@ -152,6 +160,8 @@ public class FileServer implements IncomingEvent {
}
public int getPlainPort() {
+ if (plainListener == null)
+ return 0;
return plainListener.getPort();
}
@@ -164,21 +174,23 @@ public class FileServer implements IncomingEvent {
public ActiveDownload createNewUserDownload(LocalImageVersion localImageData)
throws TTransferRejectedException {
Iterator<ActiveDownload> it = downloads.values().iterator();
+ final long now = System.currentTimeMillis();
int activeDownloads = 0;
while (it.hasNext()) {
ActiveDownload download = it.next();
- if (download.isComplete()) {
- // TODO: Check age (short timeout) and remove
+ if (download.isComplete(now) || download.hasReachedIdleTimeout(now)) {
+ download.cancel();
+ it.remove();
continue;
- } else {
- // Check age (long timeout) and remove
}
activeDownloads++;
}
- if (activeDownloads > Constants.MAX_DOWNLOADS)
- throw new TTransferRejectedException("Server busy. Too many running downloads.");
+ if (activeDownloads > Constants.MAX_DOWNLOADS) {
+ throw new TTransferRejectedException("Server busy. Too many running uploads (" + activeDownloads
+ + "/" + Constants.MAX_UPLOADS + ").");
+ }
// Determine src file and go
- File srcFile = new File(Configuration.getVmStoreBasePath(), localImageData.filePath);
+ File srcFile = composeAbsolutePath(localImageData);
if (!srcFile.canRead()) {
LOGGER.warn("Rejecting download of VID " + localImageData.imageVersionId + ": Missing "
+ srcFile.getPath());
@@ -204,4 +216,8 @@ 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/permissions/User.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/permissions/User.java
index 6e5e4d32..e2c86d80 100644
--- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/permissions/User.java
+++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/permissions/User.java
@@ -16,6 +16,7 @@ import org.openslx.bwlp.thrift.iface.LecturePermissions;
import org.openslx.bwlp.thrift.iface.LectureRead;
import org.openslx.bwlp.thrift.iface.LectureSummary;
import org.openslx.bwlp.thrift.iface.Role;
+import org.openslx.bwlp.thrift.iface.ShareMode;
import org.openslx.bwlp.thrift.iface.TAuthorizationException;
import org.openslx.bwlp.thrift.iface.TInternalServerError;
import org.openslx.bwlp.thrift.iface.TNotFoundException;
@@ -153,6 +154,13 @@ public class User {
} catch (SQLException e) {
throw new TInternalServerError();
}
+ // Do not allow deleting remote images if share mode is set to "auto download" and
+ // the version to delete is the latest
+ if (imageDetails.shareMode == ShareMode.DOWNLOAD
+ && imageDetails.latestVersionId.equals(imageVersionId)) {
+ throw new TAuthorizationException(AuthorizationError.NO_PERMISSION,
+ "Cannot delete latest version of image if auto-download is enabled");
+ }
// Check user permissions
if (imageDetails.userPermissions.admin)
return;
@@ -167,6 +175,14 @@ public class User {
"No permission to delete this image version");
}
+ public static void canDeleteImageOrFail(ImageDetailsRead imageDetails) throws TAuthorizationException {
+ // Check user permissions
+ if (imageDetails.userPermissions.admin)
+ return;
+ throw new TAuthorizationException(AuthorizationError.NO_PERMISSION,
+ "No permission to delete this image");
+ }
+
public static void canDownloadImageVersionOrFail(UserInfo user, String imageVersionId)
throws TAuthorizationException, TNotFoundException, TInternalServerError {
ImageDetailsRead image;
@@ -253,6 +269,31 @@ public class User {
}
}
+ public static boolean canEditLecturePermissions(UserInfo user, String lectureId)
+ throws TNotFoundException, TInternalServerError {
+ LectureSummary lecture = getLectureFromId(user, lectureId);
+ return lecture.userPermissions.admin;
+ }
+
+ public static void canEditLecturePermissionsOrFail(UserInfo user, String lectureId)
+ throws TAuthorizationException, TNotFoundException, TInternalServerError {
+ if (!canEditLecturePermissions(user, lectureId)) {
+ throw new TAuthorizationException(AuthorizationError.NO_PERMISSION,
+ "No permission to edit permissions");
+ }
+ }
+
+ public static void canChangeLectureOwnerOrFail(UserInfo user, String lectureId)
+ throws TAuthorizationException, TNotFoundException, TInternalServerError {
+ // TODO: Who should be allowed to change the owner? Any admin, or just the owner?
+ // Currently it's every admin, but this is open for discussion
+ LectureSummary lecture = getLectureFromId(user, lectureId);
+ if (!lecture.userPermissions.admin) {
+ throw new TAuthorizationException(AuthorizationError.NO_PERMISSION,
+ "No permission to change lecture owner");
+ }
+ }
+
public static boolean canListImages(UserInfo user) throws TAuthorizationException {
return isTutor(user);
}
diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/thrift/ServerHandler.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/thrift/ServerHandler.java
index b3e6208e..994466dd 100644
--- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/thrift/ServerHandler.java
+++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/thrift/ServerHandler.java
@@ -12,6 +12,7 @@ import org.openslx.bwlp.sat.RuntimeConfig;
import org.openslx.bwlp.sat.database.mappers.DbImage;
import org.openslx.bwlp.sat.database.mappers.DbImagePermissions;
import org.openslx.bwlp.sat.database.mappers.DbLecture;
+import org.openslx.bwlp.sat.database.mappers.DbLecturePermissions;
import org.openslx.bwlp.sat.database.mappers.DbUser;
import org.openslx.bwlp.sat.fileserv.ActiveDownload;
import org.openslx.bwlp.sat.fileserv.ActiveUpload;
@@ -28,6 +29,7 @@ import org.openslx.bwlp.thrift.iface.ImageDataError;
import org.openslx.bwlp.thrift.iface.ImageDetailsRead;
import org.openslx.bwlp.thrift.iface.ImagePermissions;
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.LecturePermissions;
import org.openslx.bwlp.thrift.iface.LectureRead;
@@ -158,8 +160,9 @@ public class ServerHandler implements SatelliteServer.Iface {
@Override
public void cancelDownload(String downloadToken) {
- // TODO Auto-generated method stub
-
+ ActiveDownload download = fileServer.getDownloadByToken(downloadToken);
+ if (download != null)
+ download.cancel();
}
/*
@@ -310,15 +313,6 @@ public class ServerHandler implements SatelliteServer.Iface {
UserInfo user = SessionManager.getOrFail(userToken);
User.canDeleteImageVersionOrFail(user, imageVersionId);
try {
- // Do not allow deleting remote images if share mode is set to "auto download" and
- // the version to delete is the latest
- ImageSummaryRead imageSummary = DbImage.getImageSummary(user,
- DbImage.getBaseIdForVersionId(imageVersionId));
- if (imageSummary.shareMode == ShareMode.DOWNLOAD
- && imageSummary.latestVersionId.equals(imageVersionId)) {
- throw new TAuthorizationException(AuthorizationError.NO_PERMISSION,
- "Cannot delete latest version of image if auto-download is enabled");
- }
DbImage.markForDeletion(imageVersionId);
} catch (SQLException e) {
throw new TInternalServerError();
@@ -328,8 +322,21 @@ public class ServerHandler implements SatelliteServer.Iface {
@Override
public void deleteImageBase(String userToken, String imageBaseId) throws TAuthorizationException,
TNotFoundException, TInternalServerError {
- // TODO Auto-generated method stub
-
+ UserInfo user = SessionManager.getOrFail(userToken);
+ ImageDetailsRead imageDetails;
+ try {
+ imageDetails = DbImage.getImageDetails(user, imageBaseId);
+ } catch (SQLException e) {
+ throw new TInternalServerError();
+ }
+ User.canDeleteImageOrFail(imageDetails);
+ for (ImageVersionDetails version : imageDetails.versions) {
+ try {
+ DbImage.markForDeletion(version.versionId);
+ } catch (Exception e) {
+ LOGGER.warn("Could not delete version when trying to delete base image", e);
+ }
+ }
}
@Override
@@ -449,22 +456,39 @@ public class ServerHandler implements SatelliteServer.Iface {
@Override
public void writeLecturePermissions(String userToken, String lectureId,
- Map<String, LecturePermissions> permissions) throws TAuthorizationException, TNotFoundException {
- // TODO Auto-generated method stub
+ Map<String, LecturePermissions> permissions) throws TAuthorizationException, TNotFoundException,
+ TInternalServerError {
+ UserInfo user = SessionManager.getOrFail(userToken);
+ User.canEditLecturePermissionsOrFail(user, lectureId);
+ try {
+ DbLecturePermissions.writeForLecture(lectureId, permissions);
+ } catch (SQLException e) {
+ throw new TInternalServerError();
+ }
}
@Override
public Map<String, LecturePermissions> getLecturePermissions(String userToken, String lectureId)
- throws TAuthorizationException, TNotFoundException {
- // TODO Auto-generated method stub
- return null;
+ throws TAuthorizationException, TNotFoundException, TInternalServerError {
+ UserInfo user = SessionManager.getOrFail(userToken);
+ boolean adminOnly = !User.canEditLecturePermissions(user, lectureId);
+ try {
+ return DbLecturePermissions.getForLecture(lectureId, adminOnly);
+ } catch (SQLException e) {
+ throw new TInternalServerError();
+ }
}
@Override
public void setLectureOwner(String userToken, String lectureId, String newOwnerId)
throws TAuthorizationException, TNotFoundException, TInternalServerError, TException {
- // TODO Auto-generated method stub
-
+ UserInfo user = SessionManager.getOrFail(userToken);
+ User.canChangeLectureOwnerOrFail(user, lectureId);
+ try {
+ DbLecture.setOwner(user, newOwnerId, lectureId);
+ } catch (SQLException e) {
+ throw new TInternalServerError();
+ }
}
@Override
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 38841cd9..c8f24e3f 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
@@ -3,6 +3,8 @@ package org.openslx.bwlp.sat.util;
import java.io.File;
import org.apache.log4j.Logger;
+import org.openslx.util.QuickTimer;
+import org.openslx.util.QuickTimer.Task;
public class FileSystem {
@@ -22,4 +24,26 @@ public class FileSystem {
return null;
return file.substring(dir.length());
}
+
+ /**
+ * Delete given file on the 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) {
+ 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());
+ }
+ }
+ });
+ }
+
}