diff options
Diffstat (limited to 'src/com/vaadin/ui')
-rw-r--r-- | src/com/vaadin/ui/DragAndDropWrapper.java | 208 | ||||
-rw-r--r-- | src/com/vaadin/ui/Html5File.java | 137 | ||||
-rw-r--r-- | src/com/vaadin/ui/Upload.java | 251 |
3 files changed, 345 insertions, 251 deletions
diff --git a/src/com/vaadin/ui/DragAndDropWrapper.java b/src/com/vaadin/ui/DragAndDropWrapper.java index 87ff571f36..afcb70506b 100644 --- a/src/com/vaadin/ui/DragAndDropWrapper.java +++ b/src/com/vaadin/ui/DragAndDropWrapper.java @@ -3,10 +3,6 @@ */ package com.vaadin.ui; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Serializable; import java.util.HashMap; import java.util.Map; @@ -19,23 +15,34 @@ import com.vaadin.event.dd.TargetDetails; import com.vaadin.event.dd.TargetDetailsImpl; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.UploadStream; +import com.vaadin.terminal.Receiver; +import com.vaadin.terminal.ReceiverOwner; import com.vaadin.terminal.gwt.client.MouseEventDetails; import com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper; import com.vaadin.terminal.gwt.client.ui.dd.HorizontalDropLocation; import com.vaadin.terminal.gwt.client.ui.dd.VerticalDropLocation; -import com.vaadin.terminal.gwt.server.AbstractApplicationServlet; -import com.vaadin.ui.DragAndDropWrapper.WrapperTransferable.Html5File; -import com.vaadin.ui.Upload.Receiver; -import com.vaadin.ui.Upload.UploadException; +import com.vaadin.ui.Html5File.ProxyReceiver; @SuppressWarnings("serial") @ClientWidget(VDragAndDropWrapper.class) public class DragAndDropWrapper extends CustomComponent implements DropTarget, - DragSource { + DragSource, ReceiverOwner { public class WrapperTransferable extends TransferableImpl { + /** + * @deprecated this class is made top level in recent version. Use + * com.vaadin.ui.Html5File instead + */ + @Deprecated + private class Html5File extends com.vaadin.ui.Html5File { + + Html5File(String name, long size, String mimeType) { + super(name, size, mimeType); + } + + } + private Html5File[] files; public WrapperTransferable(Component sourceComponent, @@ -45,13 +52,14 @@ public class DragAndDropWrapper extends CustomComponent implements DropTarget, if (fc != null) { files = new Html5File[fc]; for (int i = 0; i < fc; i++) { - Html5File file = new Html5File(); + Html5File file = new Html5File( + (String) rawVariables.get("fn" + i), // name + (Integer) rawVariables.get("fs" + i), // size + (String) rawVariables.get("ft" + i)); // mime String id = (String) rawVariables.get("fi" + i); - file.name = (String) rawVariables.get("fn" + i); - file.size = (Integer) rawVariables.get("fs" + i); - file.type = (String) rawVariables.get("ft" + i); files[i] = file; receivers.put(id, file); + requestRepaint(); // paint Receivers } } } @@ -96,50 +104,6 @@ public class DragAndDropWrapper extends CustomComponent implements DropTarget, return data; } - /** - * {@link DragAndDropWrapper} can receive also files from client - * computer if appropriate HTML 5 features are supported on client side. - * This class wraps information about dragged file on server side. - */ - public class Html5File implements Serializable { - - public String name; - private int size; - private Receiver receiver; - private String type; - - public String getFileName() { - return name; - } - - public int getFileSize() { - return size; - } - - public String getType() { - return type; - } - - /** - * Sets the {@link Receiver} that into which the file contents will - * be written. Usage of Reveiver is similar to {@link Upload} - * component. - * - * <p> - * <em>Note!</em> receiving file contents is experimental feature - * depending on HTML 5 API's. It is supported only by Firefox 3.6 at - * this time. - * - * @param receiver - * the callback that returns stream where the - * implementation writes the file contents as it arrives. - */ - public void setReceiver(Receiver receiver) { - this.receiver = receiver; - } - - } - } private Map<String, Html5File> receivers = new HashMap<String, Html5File>(); @@ -220,9 +184,23 @@ public class DragAndDropWrapper extends CustomComponent implements DropTarget, if (getDropHandler() != null) { getDropHandler().getAcceptCriterion().paint(target); } + if (receivers != null && receivers.size() > 0) { + for (String id : receivers.keySet()) { + Html5File html5File = receivers.get(id); + if (html5File.getReceiver() != null) { + target.addVariable(this, "rec-" + id, + html5File.getProxyReceiver()); + } else { + // instructs the client side not to send the file + target.addVariable(this, "rec-" + id, (String) null); + } + } + } } private DropHandler dropHandler; + private Html5File currentlyUploadedFile; + private boolean listenProgressOfUploadedFile; public DropHandler getDropHandler() { return dropHandler; @@ -251,38 +229,96 @@ public class DragAndDropWrapper extends CustomComponent implements DropTarget, return dragStartMode; } - /** - * This method should only be used by Vaadin terminal implementation. This - * is not end user api. - * - * TODO should fire progress events + end/succes events like upload. Not - * critical until we have a wider browser support for HTML5 File API - * - * @param upstream - * @param fileId - * @throws UploadException + /* + * Single controller is enough for atm as files are transferred in serial. + * If parallel transfer is needed, this logic needs to go to Html5File */ - public void receiveFile(UploadStream upstream, String fileId) - throws UploadException { - Html5File file = receivers.get(fileId); - if (file != null && file.receiver != null) { - OutputStream receiveUpload = file.receiver.receiveUpload( - file.getFileName(), "TODO"); - - InputStream stream = upstream.getStream(); - byte[] buf = new byte[AbstractApplicationServlet.MAX_BUFFER_SIZE]; - int bytesRead; - try { - while ((bytesRead = stream.read(buf)) != -1) { - receiveUpload.write(buf, 0, bytesRead); + private ReceivingController controller = new ReceivingController() { + /* + * With XHR2 file posts we can't provide as much information from the + * terminal as with multipart request. This helper class wraps the + * terminal event and provides the lacking information from the + * Html5File. + */ + class ReceivingEventWrapper implements ReceivingFailedEvent, + ReceivingEndedEvent, ReceivingStartedEvent, + ReceivingProgressedEvent { + private ReceivingEvent wrappedEvent; + + ReceivingEventWrapper(ReceivingEvent e) { + wrappedEvent = e; + } + + public String getMimeType() { + return currentlyUploadedFile.getType(); + } + + public String getFileName() { + return currentlyUploadedFile.getFileName(); + } + + public long getContentLength() { + return currentlyUploadedFile.getFileSize(); + } + + public Receiver getReceiver() { + return currentlyUploadedFile.getReceiver(); + } + + public Exception getException() { + if (wrappedEvent instanceof ReceivingFailedEvent) { + return ((ReceivingFailedEvent) wrappedEvent).getException(); } - receiveUpload.close(); - } catch (IOException e) { - throw new UploadException(e); + return null; + } + + public long getBytesReceived() { + return wrappedEvent.getBytesReceived(); + } + } + + public boolean listenProgress() { + return listenProgressOfUploadedFile; + } + + public void onProgress(ReceivingProgressedEvent event) { + currentlyUploadedFile.getUploadListener().onProgress( + new ReceivingEventWrapper(event)); + } + + public void uploadStarted(ReceivingStartedEvent event) { + currentlyUploadedFile = ((ProxyReceiver) event.getReceiver()) + .getFile(); + listenProgressOfUploadedFile = currentlyUploadedFile + .getUploadListener() != null; + if (listenProgressOfUploadedFile) { + currentlyUploadedFile.getUploadListener().uploadStarted( + new ReceivingEventWrapper(event)); + } + } + + public void uploadFinished(ReceivingEndedEvent event) { + if (listenProgressOfUploadedFile) { + currentlyUploadedFile.getUploadListener().uploadFinished( + new ReceivingEventWrapper(event)); } - // clean up the reference when file is downloaded - receivers.remove(fileId); } + public void uploadFailed(final ReceivingFailedEvent event) { + if (listenProgressOfUploadedFile) { + currentlyUploadedFile.getUploadListener().uploadFailed( + new ReceivingEventWrapper(event)); + } + } + + public boolean isInterrupted() { + return currentlyUploadedFile.isInterrupted(); + } + + }; + + public ReceivingController getReceivingController(Receiver receiver) { + return controller; } + } diff --git a/src/com/vaadin/ui/Html5File.java b/src/com/vaadin/ui/Html5File.java new file mode 100644 index 0000000000..988bf534b9 --- /dev/null +++ b/src/com/vaadin/ui/Html5File.java @@ -0,0 +1,137 @@ +package com.vaadin.ui; + +import java.io.OutputStream; +import java.io.Serializable; + +import com.vaadin.event.dd.DropHandler; +import com.vaadin.terminal.Receiver; +import com.vaadin.terminal.ReceiverOwner.ReceivingEndedEvent; +import com.vaadin.terminal.ReceiverOwner.ReceivingFailedEvent; +import com.vaadin.terminal.ReceiverOwner.ReceivingProgressedEvent; +import com.vaadin.terminal.ReceiverOwner.ReceivingStartedEvent; + +/** + * {@link DragAndDropWrapper} can receive also files from client computer if + * appropriate HTML 5 features are supported on client side. This class wraps + * information about dragged file on server side. + */ +public class Html5File implements Serializable { + + final class ProxyReceiver implements Receiver { + public OutputStream receiveUpload(String filename, String MIMEType) { + if (receiver == null) { + return null; + } + return receiver.receiveUpload(filename, MIMEType); + } + + Html5File getFile() { + return Html5File.this; + } + } + + private String name; + private long size; + private Receiver receiver; + private String type; + + Html5File(String name, long size, String mimeType) { + this.name = name; + this.size = size; + type = mimeType; + } + + /** + * The receiver that is registered to the terminal. Wraps the actual + * Receiver set later by Html5File user. + */ + private ProxyReceiver proxyReceiver = new ProxyReceiver(); + private boolean interrupted = false; + private Html5FileUploadListener listener;; + + public String getFileName() { + return name; + } + + public long getFileSize() { + return size; + } + + public String getType() { + return type; + } + + /** + * Sets the {@link Receiver} that into which the file contents will be + * written. Usage of Reveiver is similar to {@link Upload} component. + * <p> + * If the {@link Receiver} is not set in the {@link DropHandler} the file + * contents will not be sent to server. + * <p> + * <em>Note!</em> receiving file contents is experimental feature depending + * on HTML 5 API's. It is supported only by modern web brosers like Firefox + * 3.6 and above and recent webkit based browsers (Safari 5, Chrome 6) at + * this time. + * + * @param receiver + * the callback that returns stream where the implementation + * writes the file contents as it arrives. + */ + public void setReceiver(Receiver receiver) { + this.receiver = receiver; + } + + public Receiver getReceiver() { + return receiver; + } + + ProxyReceiver getProxyReceiver() { + return proxyReceiver; + } + + /** + * Gets the {@link Html5FileUploadListener} that is used to track the progress of + * streaming the file contents to given {@link Receiver}. + * + * @return + */ + public Html5FileUploadListener getUploadListener() { + return listener; + } + + /** + * Sets the {@link Html5FileUploadListener} that can be used to track the progress of + * streaming the file contents to given {@link Receiver}. + * + * @param listener + * @see #setReceiver(Receiver) + */ + public void setUploadListener(Html5FileUploadListener listener) { + this.listener = listener; + } + + public boolean isInterrupted() { + return interrupted; + } + + /** + * Interrupts uploading this file. + * + * @param interrupted + */ + public void setInterrupted(boolean interrupted) { + this.interrupted = interrupted; + } + + public interface Html5FileUploadListener { + + void onProgress(ReceivingProgressedEvent event); + + void uploadStarted(ReceivingStartedEvent event); + + void uploadFinished(ReceivingEndedEvent event); + + void uploadFailed(ReceivingFailedEvent event); + } + +}
\ No newline at end of file diff --git a/src/com/vaadin/ui/Upload.java b/src/com/vaadin/ui/Upload.java index b281d90b09..1acc91031e 100644 --- a/src/com/vaadin/ui/Upload.java +++ b/src/com/vaadin/ui/Upload.java @@ -4,20 +4,18 @@ package com.vaadin.ui; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.io.Serializable; import java.lang.reflect.Method; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Map; -import com.vaadin.Application; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.UploadStream; +import com.vaadin.terminal.ReceiverOwner; import com.vaadin.terminal.gwt.client.ui.VUpload; +import com.vaadin.terminal.gwt.server.NoInputStreamException; +import com.vaadin.terminal.gwt.server.NoOutputStreamException; import com.vaadin.ui.ClientWidget.LoadStyle; /** @@ -62,12 +60,8 @@ import com.vaadin.ui.ClientWidget.LoadStyle; */ @SuppressWarnings("serial") @ClientWidget(value = VUpload.class, loadStyle = LoadStyle.LAZY) -public class Upload extends AbstractComponent implements Component.Focusable { - - /** - * Upload buffer size. - */ - private static final int BUFFER_SIZE = 64 * 1024; // 64k +public class Upload extends AbstractComponent implements Component.Focusable, + ReceiverOwner { /** * Should the field be focused on next repaint? @@ -131,105 +125,6 @@ public class Upload extends AbstractComponent implements Component.Focusable { } /** - * This method is called by terminal when upload is received. - * - * Note, this method is called outside synchronized (Application) block, so - * overriding this may be dangerous. - * - * @param upload - */ - public void receiveUpload(UploadStream upload) throws UploadException { - if (receiver == null) { - throw new IllegalStateException( - "Receiver not set for the Upload component"); - } - - if (!isUploading) { - throw new IllegalStateException("uploading not started"); - } - - // Gets file properties - final String filename = upload.getContentName(); - final String type = upload.getContentType(); - - final Application application = getApplication(); - - synchronized (application) { - fireStarted(filename, type); - } - - // Gets the output target stream - final OutputStream out = receiver.receiveUpload(filename, type); - if (out == null) { - synchronized (application) { - fireNoOutputStream(filename, type, 0); - endUpload(); - } - return; - } - - final InputStream in = upload.getStream(); - - if (null == in) { - // No file, for instance non-existent filename in html upload - synchronized (application) { - fireNoInputStream(filename, type, 0); - endUpload(); - } - return; - } - - final byte buffer[] = new byte[BUFFER_SIZE]; - int bytesRead = 0; - totalBytes = 0; - try { - while ((bytesRead = in.read(buffer)) > 0) { - out.write(buffer, 0, bytesRead); - totalBytes += bytesRead; - if (contentLength > 0 - && (progressListeners != null || progressListener != null)) { - // update progress if listener set and contentLength - // received - synchronized (application) { - fireUpdateProgress(totalBytes, contentLength); - } - } - if (interrupted) { - throw new UploadInterruptedException(); - } - } - - // upload successful - out.close(); - synchronized (application) { - fireUploadSuccess(filename, type, totalBytes); - endUpload(); - requestRepaint(); - } - - } catch (final Exception e) { - synchronized (application) { - if (e instanceof UploadInterruptedException) { - // Download interrupted - try { - // still try to close output stream - out.close(); - } catch (IOException e1) { - // NOP - } - } - fireUploadInterrupted(filename, type, totalBytes, e); - endUpload(); - interrupted = false; - if (!(e instanceof UploadInterruptedException)) { - // throw exception for terminal to be handled - throw new UploadException(e); - } - } - } - } - - /** * Invoked when the value of a variable has changed. * * @see com.vaadin.ui.AbstractComponent#changeVariables(java.lang.Object, @@ -278,6 +173,9 @@ public class Upload extends AbstractComponent implements Component.Focusable { target.addAttribute("nextid", nextid); + // Post file to this receiver + target.addVariable(this, "action", receiver); + } /** @@ -288,20 +186,11 @@ public class Upload extends AbstractComponent implements Component.Focusable { * @version * @VERSION@ * @since 3.0 + * @deprecated use {@link com.vaadin.terminal.Receiver} instead. A "copy" + * here is kept for backwards compatibility. */ - public interface Receiver extends Serializable { - - /** - * Invoked when a new upload arrives. - * - * @param filename - * the desired filename of the upload, usually as specified - * by the client. - * @param MIMEType - * the MIME type of the uploaded file. - * @return Stream to which the uploaded file should be written. - */ - public OutputStream receiveUpload(String filename, String MIMEType); + @Deprecated + public interface Receiver extends com.vaadin.terminal.Receiver { } /* Upload events */ @@ -332,19 +221,6 @@ public class Upload extends AbstractComponent implements Component.Focusable { } } - private class UploadInterruptedException extends Exception { - public UploadInterruptedException() { - super("Upload interrupted by other thread"); - } - - } - - public static class UploadException extends Exception { - public UploadException(Exception e) { - super("Upload failed", e); - } - } - /** * Upload.Received event is sent when the upload receives a file, regardless * of whether the reception was successful or failed. If you wish to @@ -356,7 +232,7 @@ public class Upload extends AbstractComponent implements Component.Focusable { * @VERSION@ * @since 3.0 */ - public class FinishedEvent extends Component.Event { + public static class FinishedEvent extends Component.Event { /** * Length of the received file. @@ -439,7 +315,7 @@ public class Upload extends AbstractComponent implements Component.Focusable { * @VERSION@ * @since 3.0 */ - public class FailedEvent extends FinishedEvent { + public static class FailedEvent extends FinishedEvent { private Exception reason = null; @@ -484,7 +360,7 @@ public class Upload extends AbstractComponent implements Component.Focusable { /** * FailedEvent that indicates that an output stream could not be obtained. */ - public class NoOutputStreamEvent extends FailedEvent { + public static class NoOutputStreamEvent extends FailedEvent { /** * @@ -502,7 +378,7 @@ public class Upload extends AbstractComponent implements Component.Focusable { /** * FailedEvent that indicates that an input stream could not be obtained. */ - public class NoInputStreamEvent extends FailedEvent { + public static class NoInputStreamEvent extends FailedEvent { /** * @@ -526,7 +402,7 @@ public class Upload extends AbstractComponent implements Component.Focusable { * @VERSION@ * @since 3.0 */ - public class SucceededEvent extends FinishedEvent { + public static class SucceededEvent extends FinishedEvent { /** * @@ -550,10 +426,14 @@ public class Upload extends AbstractComponent implements Component.Focusable { * @VERSION@ * @since 5.0 */ - public class StartedEvent extends Component.Event { + public static class StartedEvent extends Component.Event { private final String filename; private final String type; + /** + * Length of the received file. + */ + private final long length; /** * @@ -562,10 +442,12 @@ public class Upload extends AbstractComponent implements Component.Focusable { * @param MIMEType * @param length */ - public StartedEvent(Upload source, String filename, String MIMEType) { + public StartedEvent(Upload source, String filename, String MIMEType, + long contentLength) { super(source); this.filename = filename; type = MIMEType; + length = contentLength; } /** @@ -595,6 +477,13 @@ public class Upload extends AbstractComponent implements Component.Focusable { return type; } + /** + * @return the length of the file that is being uploaded + */ + public long getContentLength() { + return length; + } + } /** @@ -786,19 +675,8 @@ public class Upload extends AbstractComponent implements Component.Focusable { * @param length */ protected void fireStarted(String filename, String MIMEType) { - fireEvent(new Upload.StartedEvent(this, filename, MIMEType)); - } - - /** - * Emit upload finished event. - * - * @param filename - * @param MIMEType - * @param length - */ - protected void fireUploadReceived(String filename, String MIMEType, - long length) { - fireEvent(new Upload.FinishedEvent(this, filename, MIMEType, length)); + fireEvent(new Upload.StartedEvent(this, filename, MIMEType, + contentLength)); } /** @@ -914,15 +792,6 @@ public class Upload extends AbstractComponent implements Component.Focusable { } /** - * Sets the size of the file currently being uploaded. - * - * @param contentLength - */ - public void setUploadSize(long contentLength) { - this.contentLength = contentLength; - } - - /** * Go into upload state. This is to prevent double uploading on same * component. * @@ -959,6 +828,8 @@ public class Upload extends AbstractComponent implements Component.Focusable { private void endUpload() { isUploading = false; contentLength = -1; + interrupted = false; + requestRepaint(); } public boolean isUploading() { @@ -1046,4 +917,54 @@ public class Upload extends AbstractComponent implements Component.Focusable { this.buttonCaption = buttonCaption; } + /* + * Handle to terminal via Upload monitors and controls the upload during it + * is being streamed. + */ + private final ReceivingController controller = new ReceivingController() { + public boolean listenProgress() { + return (progressListeners != null || progressListener != null); + } + + public void onProgress(ReceivingProgressedEvent event) { + fireUpdateProgress(event.getBytesReceived(), + event.getContentLength()); + } + + public void uploadStarted(ReceivingStartedEvent event) { + startUpload(); + contentLength = event.getContentLength(); + fireStarted(event.getFileName(), event.getMimeType()); + } + + public void uploadFinished(ReceivingEndedEvent event) { + fireUploadSuccess(event.getFileName(), event.getMimeType(), + event.getContentLength()); + endUpload(); + requestRepaint(); + } + + public void uploadFailed(ReceivingFailedEvent event) { + Exception exception = event.getException(); + if (exception instanceof NoInputStreamException) { + fireNoInputStream(event.getFileName(), event.getMimeType(), 0); + } else if (exception instanceof NoOutputStreamException) { + fireNoOutputStream(event.getFileName(), event.getMimeType(), 0); + } else { + fireUploadInterrupted(event.getFileName(), event.getMimeType(), + 0, exception); + } + endUpload(); + } + + public boolean isInterrupted() { + return interrupted; + } + }; + + public final ReceivingController getReceivingController( + com.vaadin.terminal.Receiver receiver) { + return controller; + } + } |