package org.vaadin.easyuploads; import java.io.File; import java.io.OutputStream; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.logging.Level; import java.util.logging.Logger; import org.vaadin.easyuploads.MultiUpload.FileDetail; import org.vaadin.easyuploads.UploadField.FieldType; import com.vaadin.event.dd.DragAndDropEvent; import com.vaadin.event.dd.DropHandler; import com.vaadin.event.dd.acceptcriteria.AcceptAll; import com.vaadin.event.dd.acceptcriteria.AcceptCriterion; import com.vaadin.server.StreamVariable; import com.vaadin.server.StreamVariable.StreamingEndEvent; import com.vaadin.server.StreamVariable.StreamingErrorEvent; import com.vaadin.server.StreamVariable.StreamingProgressEvent; import com.vaadin.server.StreamVariable.StreamingStartEvent; import com.vaadin.server.VaadinSession; import com.vaadin.server.WebBrowser; import com.vaadin.ui.Component; import com.vaadin.ui.CssLayout; import com.vaadin.ui.DragAndDropWrapper; import com.vaadin.ui.DragAndDropWrapper.WrapperTransferable; import com.vaadin.ui.Html5File; import com.vaadin.ui.Label; import com.vaadin.ui.ProgressIndicator; /** * MultiFileUpload makes it easier to upload multiple files. MultiFileUpload * releases upload button for new uploads immediately when a file is selected * (aka parallel uploads). It also displays progress indicators for pending * uploads. * <p> * MultiFileUpload always streams straight to files to keep memory consumption * low. To temporary files by default, but this can be overridden with * {@link #setFileFactory(FileFactory)} (eg. straight to target directory on the * server). * <p> * Developer handles uploaded files by implementing the abstract * {@link #handleFile(File, String, String, long)} method. * <p> * TODO Field version (type == Collection<File> or File where isDirectory() == * true). * <p> * TODO a super progress indicator (total transferred per total, including * queued files) * <p> * TODO Time remaining estimates and current transfer rate * */ @SuppressWarnings("serial") public abstract class MultiFileUpload extends CssLayout implements DropHandler { private CssLayout progressBars = new CssLayout(); private CssLayout uploads = new CssLayout(); private String uploadButtonCaption = "..."; public MultiFileUpload() { setWidth("200px"); addComponent(progressBars); uploads.setStyleName("v-multifileupload-uploads"); addComponent(uploads); prepareUpload(); } private void prepareUpload() { final FileBuffer receiver = createReceiver(); final MultiUpload upload = new MultiUpload(); MultiUploadHandler handler = new MultiUploadHandler() { private LinkedList<ProgressIndicator> indicators; public void streamingStarted(StreamingStartEvent event) { } public void streamingFinished(StreamingEndEvent event) { if (!indicators.isEmpty()) { progressBars.removeComponent(indicators.remove(0)); } File file = receiver.getFile(); handleFile(file, event.getFileName(), event.getMimeType(), event.getBytesReceived()); receiver.setValue(null); } public void streamingFailed(StreamingErrorEvent event) { Logger.getLogger(getClass().getName()).log(Level.FINE, "Streaming failed", event.getException()); for (ProgressIndicator progressIndicator : indicators) { progressBars.removeComponent(progressIndicator); } } public void onProgress(StreamingProgressEvent event) { long readBytes = event.getBytesReceived(); long contentLength = event.getContentLength(); float f = (float) readBytes / (float) contentLength; indicators.get(0).setValue(f); } public OutputStream getOutputStream() { FileDetail next = upload.getPendingFileNames().iterator() .next(); return receiver.receiveUpload(next.getFileName(), next.getMimeType()); } public void filesQueued(Collection<FileDetail> pendingFileNames) { if (indicators == null) { indicators = new LinkedList<ProgressIndicator>(); } for (FileDetail f : pendingFileNames) { ProgressIndicator pi = createProgressIndicator(); progressBars.addComponent(pi); pi.setCaption(f.getFileName()); pi.setVisible(true); indicators.add(pi); } } @Override public boolean isInterrupted() { return false; } }; upload.setHandler(handler); upload.setButtonCaption(getUploadButtonCaption()); uploads.addComponent(upload); } private ProgressIndicator createProgressIndicator() { ProgressIndicator progressIndicator = new ProgressIndicator(); progressIndicator.setPollingInterval(300); progressIndicator.setValue(0f); return progressIndicator; } public String getUploadButtonCaption() { return uploadButtonCaption; } public void setUploadButtonCaption(String uploadButtonCaption) { this.uploadButtonCaption = uploadButtonCaption; Iterator<Component> componentIterator = uploads.getComponentIterator(); while (componentIterator.hasNext()) { Component next = componentIterator.next(); if (next instanceof MultiUpload) { MultiUpload upload = (MultiUpload) next; if (upload.isVisible()) { upload.setButtonCaption(getUploadButtonCaption()); } } } } private FileFactory fileFactory; public FileFactory getFileFactory() { if (fileFactory == null) { fileFactory = new TempFileFactory(); } return fileFactory; } public void setFileFactory(FileFactory fileFactory) { this.fileFactory = fileFactory; } protected FileBuffer createReceiver() { FileBuffer receiver = new FileBuffer(FieldType.FILE) { @Override public FileFactory getFileFactory() { return MultiFileUpload.this.getFileFactory(); } }; return receiver; } protected int getPollinInterval() { return 500; } @Override public void attach() { super.attach(); if (supportsFileDrops()) { prepareDropZone(); } } private DragAndDropWrapper dropZone; /** * Sets up DragAndDropWrapper to accept multi file drops. */ private void prepareDropZone() { if (dropZone == null) { Component label = new Label(getAreaText(), Label.CONTENT_XHTML); label.setSizeUndefined(); dropZone = new DragAndDropWrapper(label); dropZone.setStyleName("v-multifileupload-dropzone"); dropZone.setSizeUndefined(); addComponent(dropZone, 1); dropZone.setDropHandler(this); addStyleName("no-horizontal-drag-hints"); addStyleName("no-vertical-drag-hints"); } } protected String getAreaText() { return "<small>DROP<br/>FILES</small>"; } @SuppressWarnings("deprecation") protected boolean supportsFileDrops() { WebBrowser browser = getUI().getPage().getWebBrowser(); if (browser.isChrome()) { return true; } else if (browser.isFirefox()) { return true; } else if (browser.isSafari()) { return true; } return false; } abstract protected void handleFile(File file, String fileName, String mimeType, long length); /** * A helper method to set DirectoryFileFactory with given pathname as * directory. * * @param file */ public void setRootDirectory(String directoryWhereToUpload) { setFileFactory(new DirectoryFileFactory( new File(directoryWhereToUpload))); } public AcceptCriterion getAcceptCriterion() { // TODO accept only files // return new And(new TargetDetailIs("verticalLocation","MIDDLE"), new // TargetDetailIs("horizontalLoction", "MIDDLE")); return AcceptAll.get(); } public void drop(DragAndDropEvent event) { DragAndDropWrapper.WrapperTransferable transferable = (WrapperTransferable) event .getTransferable(); Html5File[] files = transferable.getFiles(); for (final Html5File html5File : files) { final ProgressIndicator pi = new ProgressIndicator(); pi.setCaption(html5File.getFileName()); progressBars.addComponent(pi); final FileBuffer receiver = createReceiver(); html5File.setStreamVariable(new StreamVariable() { private String name; private String mime; public OutputStream getOutputStream() { return receiver.receiveUpload(name, mime); } public boolean listenProgress() { return true; } public void onProgress(StreamingProgressEvent event) { float p = (float) event.getBytesReceived() / (float) event.getContentLength(); pi.setValue(p); } public void streamingStarted(StreamingStartEvent event) { name = event.getFileName(); mime = event.getMimeType(); } public void streamingFinished(StreamingEndEvent event) { progressBars.removeComponent(pi); handleFile(receiver.getFile(), html5File.getFileName(), html5File.getType(), html5File.getFileSize()); receiver.setValue(null); } public void streamingFailed(StreamingErrorEvent event) { progressBars.removeComponent(pi); } public boolean isInterrupted() { return false; } }); } } }