I'm trying to use Vaadin 8 FileDropTarget with a Tomcat server. The example code is little bit long, but maybe it will fit here:
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
import com.vaadin.server.StreamVariable;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Label;
import com.vaadin.ui.Notification;
import com.vaadin.ui.ProgressBar;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.dnd.FileDropTarget;
import hu.dynaxpress.base.mvc.DynaAbstractController;
@SuppressWarnings({ "serial", "rawtypes" })
public class FileStreamController extends DynaAbstractController {
private VerticalLayout layout;
private ProgressBar progress;
@Override
protected void onInit() {
super.onInit();
System.err.println("FileStreamController.onInit");
}
@Override
protected void onEnter() {
System.err.println("FileStreamController.onEnter");
// https://vaadin.com/docs/v8/framework/components/components-upload.html
layout = new VerticalLayout();
final Label infoLabel = new Label("DROP A FILE ON THIS TEXT");
infoLabel.setWidth(240.0f, Unit.PIXELS);
final VerticalLayout dropPane = new VerticalLayout(infoLabel);
dropPane.setComponentAlignment(infoLabel, Alignment.MIDDLE_CENTER);
dropPane.addStyleName("drop-area");
dropPane.setSizeUndefined();
dropPane.setWidth("100%");
progress = new ProgressBar();
progress.setIndeterminate(false);
progress.setVisible(false);
progress.setWidth("100%");
dropPane.addComponent(progress);
layout.addComponent(dropPane);
new FileDropTarget<>(dropPane, fileDropEvent -> {
final long fileSizeLimit = 20 * 1024 * 1024 * 1024; // 20GB
fileDropEvent.getFiles().forEach(html5File -> {
// html5File.getFileSize() always returns zero, but why?
if (false && ( html5File.getFileSize() > fileSizeLimit) ) {
Notification.show(
"File rejected. Max size=" +fileSizeLimit+ ", actual="+html5File.getFileSize(),
Notification.Type.WARNING_MESSAGE);
} else {
Label lbl = new Label(html5File.getFileName() + " size=" + html5File.getFileSize() + " " + html5File.getType());
lbl.setWidth("100%");
layout.addComponent(lbl);
final StreamVariable streamVariable = new StreamVariable() {
@Override
public OutputStream getOutputStream() {
try {
return new FileOutputStream("F:\\"+html5File.getFileName());
} catch (FileNotFoundException e) {
e.printStackTrace();
throw new Error("F:\\"+html5File.getFileName());
}
}
@Override
public boolean listenProgress() { return false; }
@Override
public void onProgress(final StreamingProgressEvent event) {
progress.setValue(
event.getBytesReceived() / event.getContentLength()
);
}
@Override
public void streamingStarted(
final StreamingStartEvent event) {
progress.setVisible(true);
progress.setCaption(event.getFileName());
}
@Override
public void streamingFinished(
final StreamingEndEvent event) {
progress.setVisible(false);
}
@Override
public void streamingFailed(final StreamingErrorEvent event) {
progress.setVisible(false);
}
@Override
public boolean isInterrupted() { return false; }
};
html5File.setStreamVariable(streamVariable);
progress.setVisible(true);
}
});
});
setCompositionRoot(layout);
}
}
This code works, but it has terrible performance. In this MWE, the program writes all dropped files to the F:\ drive. It is a flash drive (I wanted to simulate streaming large files and see how much memory is consumed over time). But I found that this program writes out a 40MB file in 20 seconds. That is 2MB/sec speed. The server runs on localhost, the source file and the target F: drive are all on my local machine. If I simply copy the same file to the drive then it takes less than 2 seconds.
I have tried with another (very slow) flash drive too, and had similar results. When tomcat is streaming the file, then it is about 10-20 times slower.
Is there a way to make this faster? What am I doing wrong? I do not see any way to speed this up, because the streaming happens inside the component.
The second important (no related) question is that why can't I get the file size? See the comment in the code: "html5File.getFileSize() always returns zero, but why?" I have checked the POST headers from firefox debugger, and the browser sends the size of the file in an JSON/RPC call, before it sends the actual file data. So the server should know the file size before the first byte arrives.