package org.openslx.bwlp.sat.fileserv;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
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.Formatter;
import org.openslx.bwlp.sat.util.Identity;
import org.openslx.bwlp.thrift.iface.ImageDetailsRead;
import org.openslx.bwlp.thrift.iface.TTransferRejectedException;
import org.openslx.bwlp.thrift.iface.UserInfo;
import org.openslx.filetransfer.Downloader;
import org.openslx.filetransfer.IncomingEvent;
import org.openslx.filetransfer.Listener;
import org.openslx.filetransfer.Uploader;
public class FileServer implements IncomingEvent {
private static final Logger LOGGER = Logger.getLogger(FileServer.class);
/**
* Listener for incoming unencrypted connections
*/
private final Listener plainListener = new Listener(this, null, 9092, Constants.TRANSFER_TIMEOUT); // TODO: Config
private final Listener sslListener;
private final ThreadPoolExecutor transferPool = new ThreadPoolExecutor(2, Constants.MAX_UPLOADS
+ Constants.MAX_DOWNLOADS, 1, TimeUnit.MINUTES, new ArrayBlockingQueue<Runnable>(1));
/**
* All currently running uploads, indexed by token
*/
private final Map<String, ActiveUpload> uploads = new ConcurrentHashMap<>();
/**
* All currently running downloads, indexed by token
*/
private final Map<String, ActiveDownload> downloads = new ConcurrentHashMap<>();
private static final FileServer globalInstance = new FileServer();
private FileServer() {
SSLContext ctx = Identity.getSSLContext();
sslListener = ctx == null ? null : new Listener(this, ctx, 9093, Constants.TRANSFER_TIMEOUT);
}
public static FileServer instance() {
return globalInstance;
}
public boolean start() {
boolean ret = plainListener.start();
if (sslListener != null) {
ret |= sslListener.start();
}
return ret;
}
@Override
public void incomingDownloadRequest(Uploader uploader) throws IOException {
String token = uploader.getToken();
LOGGER.info("Incoming filetransfer with token " + token);
ActiveDownload download = downloads.get(token);
if (download == null) {
LOGGER.warn("Unknown token " + token);
uploader.cancel();
return;
}
if (!download.addConnection(uploader, transferPool)) {
uploader.cancel();
}
}
@Override
public void incomingUploadRequest(Downloader downloader) throws IOException {
String token = downloader.getToken();
LOGGER.info("Incoming filetransfer with token " + token);
ActiveUpload upload = uploads.get(token);
if (upload == null) {
LOGGER.warn("Unknown token " + token);
downloader.cancel();
return;
}
if (!upload.addConnection(downloader, transferPool)) {
downloader.cancel();
}
}
/**
* Get an upload instance by given token.
*
* @param uploadToken
* @return
*/
public ActiveUpload getUploadByToken(String uploadToken) {
if (uploadToken == null)
return null;
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(now) || upload.hasReachedIdleTimeout(now)) {
upload.cancel();
it.remove();
continue;
}
activeUploads++;
}
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();
} while (destinationFile.exists());
destinationFile.getParentFile().mkdirs();
String key = UUID.randomUUID().toString();
ActiveUpload upload;
try {
upload = new ActiveUpload(key, owner, image, destinationFile, fileSize, sha1Sums,
machineDescription);
} catch (FileNotFoundException e) {
LOGGER.error("Could not open destination file for writing", e);
throw new TTransferRejectedException("Destination file not writable!");
}
uploads.put(key, upload);
return upload;
}
public int getPlainPort() {
if (plainListener == null)
return 0;
return plainListener.getPort();
}
public int getSslPort() {
if (sslListener == null)
return 0;
return sslListener.getPort();
}
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(now) || download.hasReachedIdleTimeout(now)) {
download.cancel();
it.remove();
continue;
}
activeDownloads++;
}
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 = composeAbsolutePath(localImageData);
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");
}
if (srcFile.length() != localImageData.fileSize) {
LOGGER.warn("Rejecting download of VID " + localImageData.imageVersionId + ": Size mismatch for "
+ srcFile.getPath() + " (expected " + localImageData.fileSize + ", is "
+ srcFile.length() + ")");
try {
DbImage.markValid(false, true, localImageData);
} catch (SQLException e) {
}
throw new TTransferRejectedException("File corrupted on server");
}
String key = UUID.randomUUID().toString();
ActiveDownload transfer = new ActiveDownload(key, srcFile);
downloads.put(key, transfer);
return transfer;
}
public static File composeAbsolutePath(LocalImageVersion localImageData) {
return new File(Configuration.getVmStoreBasePath(), localImageData.filePath);
}
}