From: Matti Tahvonen Date: Thu, 27 Sep 2007 08:43:27 +0000 (+0000) Subject: Streaming upload and client side implementation for upload + progressindicator X-Git-Tag: 6.7.0.beta1~5952 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=749b99fd463276848ecbbfbbe68c850f9e313beb;p=vaadin-framework.git Streaming upload and client side implementation for upload + progressindicator svn changeset:2381/svn branch:trunk --- diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ApplicationConnection.java b/src/com/itmill/toolkit/terminal/gwt/client/ApplicationConnection.java index 9069bbe6d1..8a5d55bfe1 100755 --- a/src/com/itmill/toolkit/terminal/gwt/client/ApplicationConnection.java +++ b/src/com/itmill/toolkit/terminal/gwt/client/ApplicationConnection.java @@ -74,7 +74,7 @@ public class ApplicationConnection implements FocusListener { return re.test(uri); }-*/; - private native String getAppUri()/*-{ + public native String getAppUri()/*-{ return $wnd.itmtk.appUri; }-*/; diff --git a/src/com/itmill/toolkit/terminal/gwt/client/DefaultWidgetSet.java b/src/com/itmill/toolkit/terminal/gwt/client/DefaultWidgetSet.java index d0ccf5d800..2f67f6fbf4 100644 --- a/src/com/itmill/toolkit/terminal/gwt/client/DefaultWidgetSet.java +++ b/src/com/itmill/toolkit/terminal/gwt/client/DefaultWidgetSet.java @@ -20,6 +20,7 @@ import com.itmill.toolkit.terminal.gwt.client.ui.IOrderedLayoutVertical; import com.itmill.toolkit.terminal.gwt.client.ui.IPanel; import com.itmill.toolkit.terminal.gwt.client.ui.IPasswordField; import com.itmill.toolkit.terminal.gwt.client.ui.IPopupCalendar; +import com.itmill.toolkit.terminal.gwt.client.ui.IProgressIndicator; import com.itmill.toolkit.terminal.gwt.client.ui.IScrollTable; import com.itmill.toolkit.terminal.gwt.client.ui.ISelect; import com.itmill.toolkit.terminal.gwt.client.ui.ISlider; @@ -151,7 +152,11 @@ public class DefaultWidgetSet implements WidgetSet { } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IFilterSelect" .equals(className)) { return new IFilterSelect(); + } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IProgressIndicator" + .equals(className)) { + return new IProgressIndicator(); } + return new IUnknownComponent(); /* @@ -241,6 +246,8 @@ public class DefaultWidgetSet implements WidgetSet { return "com.itmill.toolkit.terminal.gwt.client.ui.ISplitPanelHorizontal"; } else if ("vsplitpanel".equals(tag)) { return "com.itmill.toolkit.terminal.gwt.client.ui.ISplitPanelVertical"; + } else if ("progressindicator".equals(tag)) { + return "com.itmill.toolkit.terminal.gwt.client.ui.IProgressIndicator"; } return "com.itmill.toolkit.terminal.gwt.client.ui.IUnknownComponent"; diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/IProgressIndicator.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/IProgressIndicator.java new file mode 100644 index 0000000000..d0a5b1c6dc --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/IProgressIndicator.java @@ -0,0 +1,54 @@ +package com.itmill.toolkit.terminal.gwt.client.ui; + +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; + +public class IProgressIndicator extends Widget implements Paintable { + + private static final String CLASSNAME = "i-progressindicator"; + Element wrapper = DOM.createDiv(); + Element indicator = DOM.createDiv(); + private ApplicationConnection client; + private Poller poller; + + public IProgressIndicator() { + setElement(wrapper); + setStyleName(CLASSNAME); + DOM.appendChild(wrapper, indicator); + poller = new Poller(); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + poller.cancel(); + this.client = client; + if(client.updateComponent(this, uidl, true)) + return; + boolean indeterminate = uidl.getBooleanAttribute("indeterminate"); + + if(indeterminate) { + // TODO put up some image or something + } else { + try { + float f = Float.parseFloat(uidl.getStringAttribute("state")); + int size = Math.round(100*f); + DOM.setStyleAttribute(indicator, "width", size + "%"); + } catch (Exception e) { + } + } + poller.scheduleRepeating(uidl.getIntAttribute("pollinginterval")); + } + + class Poller extends Timer { + + public void run() { + client.sendPendingVariableChanges(); + } + + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/IUpload.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/IUpload.java index b1c17adf66..b43293cdc1 100644 --- a/src/com/itmill/toolkit/terminal/gwt/client/ui/IUpload.java +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/IUpload.java @@ -1,5 +1,6 @@ package com.itmill.toolkit.terminal.gwt.client.ui; +import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.ClickListener; import com.google.gwt.user.client.ui.FileUpload; @@ -8,58 +9,68 @@ import com.google.gwt.user.client.ui.FormHandler; import com.google.gwt.user.client.ui.FormPanel; import com.google.gwt.user.client.ui.FormSubmitCompleteEvent; import com.google.gwt.user.client.ui.FormSubmitEvent; -import com.google.gwt.user.client.ui.Frame; -import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.Widget; import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; import com.itmill.toolkit.terminal.gwt.client.Paintable; import com.itmill.toolkit.terminal.gwt.client.UIDL; -public class IUpload extends FormPanel implements Paintable, ClickListener { - +public class IUpload extends FormPanel implements Paintable, ClickListener, + FormHandler { + + /** + * FileUpload component that opens native OS dialog to select file. + */ FileUpload fu = new FileUpload(); - + Panel panel = new FlowPanel(); - + ApplicationConnection client; private String paintableId; + /** + * Button that initiates uploading + */ private Button b; - + + /** + * When expecting big files, programmer may initiate some UI changes when + * uploading the file starts. Bit after submitting file we'll visit the + * server to check possible changes. + */ + private Timer t; + + /** + * some browsers tries to send form twice if submit is called in button + * click handler, some don't submit at all without it, so we need to track + * if form is already being submitted + */ + private boolean submitted = false; + public IUpload() { super(); setEncoding(FormPanel.ENCODING_MULTIPART); - setMethod(FormPanel.METHOD_POST); + setMethod(FormPanel.METHOD_POST); + setWidget(panel); - panel.add(new Label("UPLOAD component incomplete")); panel.add(fu); - b = new Button("Upload"); + // TODO + b = new Button("Click to Upload"); b.addClickListener(this); panel.add(b); - - addFormHandler(new FormHandler() { - public void onSubmitComplete(FormSubmitCompleteEvent event) { - if(client != null) { - // request update - client.sendPendingVariableChanges(); - } - } - - public void onSubmit(FormSubmitEvent event) { - if (fu.getFilename().length() == 0) { - event.setCancelled(true); - } - } - }); + + addFormHandler(this); } public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { this.client = client; this.paintableId = uidl.getId(); - if(uidl.hasAttribute("caption")) + setAction(client.getAppUri()); + + if (uidl.hasAttribute("caption")) b.setText(uidl.getStringAttribute("caption")); + fu.setName(paintableId + "_file"); } @@ -67,5 +78,49 @@ public class IUpload extends FormPanel implements Paintable, ClickListener { this.submit(); } + public void onSubmit(FormSubmitEvent event) { + if (fu.getFilename().length() == 0 || submitted) { + event.setCancelled(true); + ApplicationConnection.getConsole().log( + "Submit cancelled (no file or already submitted)"); + return; + } + submitted = true; + ApplicationConnection.getConsole().log("Submitted form"); + + disableUpload(); + + /* + * visit server after upload to see possible changes from UploadStarted + * event + */ + t = new Timer() { + public void run() { + client.sendPendingVariableChanges(); + } + }; + t.schedule(800); + } + + protected void disableUpload() { + b.setEnabled(false); + fu.setVisible(false); + } + protected void enableUploaod() { + b.setEnabled(true); + fu.setVisible(true); + } + + public void onSubmitComplete(FormSubmitCompleteEvent event) { + if (client != null) { + if (t != null) + t.cancel(); + ApplicationConnection.getConsole().log("Submit complete"); + client.sendPendingVariableChanges(); + } + submitted = false; + enableUploaod(); + } + } diff --git a/src/com/itmill/toolkit/terminal/gwt/public/default/progressindicator/progressindicator.css b/src/com/itmill/toolkit/terminal/gwt/public/default/progressindicator/progressindicator.css new file mode 100644 index 0000000000..cd12ada993 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/public/default/progressindicator/progressindicator.css @@ -0,0 +1,8 @@ +.i-progressindicator { + background: red; + height: 20px; +} +.i-progressindicator div { + background: green; + height:100%; +} \ No newline at end of file diff --git a/src/com/itmill/toolkit/terminal/gwt/public/default/styles.css b/src/com/itmill/toolkit/terminal/gwt/public/default/styles.css index 0a2300e2c1..6b686274c0 100644 --- a/src/com/itmill/toolkit/terminal/gwt/public/default/styles.css +++ b/src/com/itmill/toolkit/terminal/gwt/public/default/styles.css @@ -12,4 +12,5 @@ @import "tree/tree.css"; @import "splitpanel/splitpanel.css"; @import "select/filterselect.css"; +@import "progressindicator/progressindicator.css"; diff --git a/src/com/itmill/toolkit/terminal/gwt/server/ApplicationServlet.java b/src/com/itmill/toolkit/terminal/gwt/server/ApplicationServlet.java index db0ff14bbf..ef4d173542 100644 --- a/src/com/itmill/toolkit/terminal/gwt/server/ApplicationServlet.java +++ b/src/com/itmill/toolkit/terminal/gwt/server/ApplicationServlet.java @@ -52,6 +52,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.xml.sax.SAXException; import com.itmill.toolkit.Application; @@ -309,6 +310,13 @@ public class ApplicationServlet extends HttpServlet { Application application = null; try { + // handle file upload if multipart request + if(ServletFileUpload.isMultipartContent(request)) { + application = getApplication(request); + getApplicationManager(application).handleFileUpload(request, response); + return; + } + // Update browser details WebBrowser browser = WebApplicationContext.getApplicationContext( request.getSession()).getBrowser(); @@ -330,11 +338,7 @@ public class ApplicationServlet extends HttpServlet { // Is this a download request from application DownloadStream download = null; - // The rest of the process is synchronized with the application - // in order to guarantee that no parallel variable handling is - // made - synchronized (application) { - + // Handles AJAX UIDL requests String resourceId = request.getPathInfo(); if (resourceId != null && resourceId.startsWith(AJAX_UIDL_URI)) { @@ -385,7 +389,6 @@ public class ApplicationServlet extends HttpServlet { writeAjaxPage(request, response, window, themeName); } - } // For normal requests, transform the window if (download != null) diff --git a/src/com/itmill/toolkit/terminal/gwt/server/CommunicationManager.java b/src/com/itmill/toolkit/terminal/gwt/server/CommunicationManager.java index 5965562f1d..d0c9af00aa 100644 --- a/src/com/itmill/toolkit/terminal/gwt/server/CommunicationManager.java +++ b/src/com/itmill/toolkit/terminal/gwt/server/CommunicationManager.java @@ -59,16 +59,25 @@ import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.commons.fileupload.FileItemIterator; +import org.apache.commons.fileupload.FileItemStream; +import org.apache.commons.fileupload.FileUploadException; +import org.apache.commons.fileupload.ProgressListener; +import org.apache.commons.fileupload.servlet.ServletFileUpload; +import org.apache.commons.fileupload.util.Streams; + import com.itmill.toolkit.Application; import com.itmill.toolkit.Application.WindowAttachEvent; import com.itmill.toolkit.Application.WindowDetachEvent; import com.itmill.toolkit.terminal.DownloadStream; import com.itmill.toolkit.terminal.Paintable; import com.itmill.toolkit.terminal.URIHandler; +import com.itmill.toolkit.terminal.UploadStream; import com.itmill.toolkit.terminal.VariableOwner; import com.itmill.toolkit.terminal.Paintable.RepaintRequestEvent; import com.itmill.toolkit.ui.Component; import com.itmill.toolkit.ui.FrameWindow; +import com.itmill.toolkit.ui.Upload; import com.itmill.toolkit.ui.Window; /** @@ -135,10 +144,93 @@ public class CommunicationManager implements Paintable.RepaintRequestListener, application.removeListener((Application.WindowDetachListener) this); } + /** + * Handles file upload request submitted via Upload component. + * + * @param request + * @param response + * @throws IOException + */ + public void handleFileUpload(HttpServletRequest request, + HttpServletResponse response) throws IOException { + // Create a new file upload handler + ServletFileUpload upload = new ServletFileUpload(); + + UploadProgressListener pl = new UploadProgressListener(); + + upload.setProgressListener(pl); + + // Parse the request + FileItemIterator iter; + + try { + iter = upload.getItemIterator(request); + /* ATM this loop is run only once as we are uploading one file per + * request. + */ + while (iter.hasNext()) { + FileItemStream item = iter.next(); + String name = item.getFieldName(); + final String filename = item.getName(); + final String mimeType = item.getContentType(); + final InputStream stream = item.openStream(); + if (item.isFormField()) { + // ignored, upload requests contian only files + } else { + String pid = name.split("_")[0]; + Upload uploadComponent = (Upload) idPaintableMap.get(pid); + if (uploadComponent == null) { + throw new FileUploadException( + "Upload component not found"); + } + synchronized (application) { + // put upload component into receiving state + uploadComponent.startUpload(); + } + UploadStream upstream = new UploadStream() { + + public String getContentName() { + return filename; + } + + public String getContentType() { + return mimeType; + } + + public InputStream getStream() { + return stream; + } + + public String getStreamName() { + return "stream"; + } + + }; + + // tell UploadProgressListener which component is receiving file + pl.setUpload(uploadComponent); + + uploadComponent.receiveUpload(upstream); + } + } + } catch (FileUploadException e) { + e.printStackTrace(); + } + + // Send short response to acknowledge client that request was done + response.setContentType("text/html"); + OutputStream out = response.getOutputStream(); + PrintWriter outWriter = new PrintWriter(new BufferedWriter( + new OutputStreamWriter(out, "UTF-8"))); + outWriter.print("download handled"); + outWriter.flush(); + out.close(); + } + public void handleUidlRequest(HttpServletRequest request, HttpServletResponse response) throws IOException { - // repaint requested or sesssion has timed out and new one is created + // repaint requested or session has timed out and new one is created boolean repaintAll = (request.getParameter(GET_PARAM_REPAINT_ALL) != null) || request.getSession().isNew(); @@ -146,10 +238,6 @@ public class CommunicationManager implements Paintable.RepaintRequestListener, PrintWriter outWriter = new PrintWriter(new BufferedWriter( new OutputStreamWriter(out, "UTF-8"))); - // TODO Move dirt elsewhere - outWriter.print(")/*{"); // some dirt to prevent cross site scripting - // vulnerabilities - try { // Is this a download request from application @@ -193,6 +281,9 @@ public class CommunicationManager implements Paintable.RepaintRequestListener, // Sets the response type response.setContentType("application/json; charset=UTF-8"); + // some dirt to prevent cross site scripting + outWriter.print(")/*{"); + outWriter.print("\"changes\":["); paintTarget = new JsonPaintTarget(this, outWriter); @@ -206,7 +297,7 @@ public class CommunicationManager implements Paintable.RepaintRequestListener, // Reset sent locales locales = null; requireLocale(application.getLocale().toString()); - + } else paintables = getDirtyComponents(); if (paintables != null) { @@ -267,17 +358,14 @@ public class CommunicationManager implements Paintable.RepaintRequestListener, w.setTerminal(application.getMainWindow() .getTerminal()); } - /* This does not seem to happen in tk5, but remember this case: - else if (p instanceof Component) { - if (((Component) p).getParent() == null - || ((Component) p).getApplication() == null) { - // Component requested repaint, but is no - // longer attached: skip - paintablePainted(p); - continue; - } - } - */ + /* + * This does not seem to happen in tk5, but remember + * this case: else if (p instanceof Component) { if + * (((Component) p).getParent() == null || + * ((Component) p).getApplication() == null) { // + * Component requested repaint, but is no // longer + * attached: skip paintablePainted(p); continue; } } + */ paintTarget.startTag("change"); paintTarget.addAttribute("format", "uidl"); String pid = getPaintableId(p); @@ -318,8 +406,9 @@ public class CommunicationManager implements Paintable.RepaintRequestListener, if (request.getParameter("theme") != null) { themeName = request.getParameter("theme"); } - if (themeName == null) themeName = "default"; - + if (themeName == null) + themeName = "default"; + // TODO We should only precache the layouts that are not // cached already int resourceIndex = 0; @@ -413,14 +502,15 @@ public class CommunicationManager implements Paintable.RepaintRequestListener, Map m; if (i + 2 >= ca.length || !vid[0].equals(ca[i + 2].split("_")[0])) { - if(ca.length > i + 1) { - m = new SingleValueMap(vid[1], convertVariableValue( - vid[2].charAt(0), ca[++i])); + if (ca.length > i + 1) { + m = new SingleValueMap(vid[1], + convertVariableValue(vid[2].charAt(0), + ca[++i])); } else { - m = new SingleValueMap(vid[1], convertVariableValue(vid[2].charAt(0), "")); + m = new SingleValueMap(vid[1], + convertVariableValue(vid[2].charAt(0), "")); } - } - else { + } else { m = new HashMap(); m.put(vid[1], convertVariableValue(vid[2].charAt(0), ca[++i])); @@ -465,8 +555,9 @@ public class CommunicationManager implements Paintable.RepaintRequestListener, val = Boolean.valueOf(strValue); break; } - - System.out.println("result: " + val + " of type " + (val == null ? "-" : val.getClass().toString())); + + System.out.println("result: " + val + " of type " + + (val == null ? "-" : val.getClass().toString())); return val; } @@ -605,7 +696,7 @@ public class CommunicationManager implements Paintable.RepaintRequestListener, // Find the window where the request is handled String path = request.getPathInfo(); - + // Remove UIDL from the path path = path.substring("/UIDL".length()); @@ -779,8 +870,7 @@ public class CommunicationManager implements Paintable.RepaintRequestListener, new OutputStreamWriter(out, "UTF-8"))); outWriter.print(")/*{"); outWriter.print("\"redirect\":{"); - outWriter.write("\"url\":\"" + logoutUrl - + "\"}"); + outWriter.write("\"url\":\"" + logoutUrl + "\"}"); outWriter.flush(); outWriter.close(); out.flush(); @@ -1067,4 +1157,26 @@ public class CommunicationManager implements Paintable.RepaintRequestListener, else return new Locale(temp[0], temp[1], temp[2]); } + + /* + * Upload progress listener notifies upload component once when Jakarta + * FileUpload can determine content length. Used to detect files total size, + * uploads progress can be tracked inside upload. + */ + private class UploadProgressListener implements ProgressListener { + Upload uploadComponent; + boolean updated = false; + + public void setUpload(Upload u) { + uploadComponent = u; + } + + public void update(long bytesRead, long contentLength, int items) { + if (!updated && uploadComponent != null) { + uploadComponent.setUploadSize(contentLength); + updated = true; + } + } + } + } diff --git a/src/com/itmill/toolkit/tests/TestForUpload.java b/src/com/itmill/toolkit/tests/TestForUpload.java new file mode 100644 index 0000000000..a981ec2f8c --- /dev/null +++ b/src/com/itmill/toolkit/tests/TestForUpload.java @@ -0,0 +1,207 @@ +package com.itmill.toolkit.tests; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; + +import com.itmill.toolkit.terminal.StreamResource; +import com.itmill.toolkit.ui.Button; +import com.itmill.toolkit.ui.CustomComponent; +import com.itmill.toolkit.ui.Label; +import com.itmill.toolkit.ui.Layout; +import com.itmill.toolkit.ui.Link; +import com.itmill.toolkit.ui.OrderedLayout; +import com.itmill.toolkit.ui.Panel; +import com.itmill.toolkit.ui.ProgressIndicator; +import com.itmill.toolkit.ui.Upload; +import com.itmill.toolkit.ui.Button.ClickEvent; +import com.itmill.toolkit.ui.Upload.FailedEvent; +import com.itmill.toolkit.ui.Upload.FailedListener; +import com.itmill.toolkit.ui.Upload.FinishedEvent; +import com.itmill.toolkit.ui.Upload.FinishedListener; +import com.itmill.toolkit.ui.Upload.StartedEvent; +import com.itmill.toolkit.ui.Upload.StartedListener; +import com.itmill.toolkit.ui.Upload.SucceededEvent; +import com.itmill.toolkit.ui.Upload.SucceededListener; + +public class TestForUpload extends CustomComponent implements + Upload.FinishedListener, FailedListener,SucceededListener, Upload.ProgressListener, StartedListener { + + Layout main = new OrderedLayout(); + + Buffer buffer = new Buffer(); + + Panel status = new Panel("Uploaded file:"); + + private Upload up; + + private Label l; + + private ProgressIndicator pi = new ProgressIndicator(); + + public TestForUpload() { + setCompositionRoot(main); + main.addComponent( new Label( + "This is a simple test for upload application. " + + "Upload should work with big files and concurrent " + + "requests should not be blocked. Button 'b' reads " + + "current state into label below it. TODO make " + + "streaming example/test where upload contents " + + "is read but not saved and memory consumption is " + + "verified low. TODO make test where contents is " + + "written to disk and verifiy low memory consumption.")); + + main.addComponent(new Label("Clicking on button b updates information about upload components status.")); + + up = new Upload("Upload", buffer); + up.setImmediate(true); + up.addListener((FinishedListener)this); + up.addListener((FailedListener) this); + up.addListener((SucceededListener) this); + up.addListener((StartedListener) this); + + + up.setProgressListener(this); + + Button b = new Button("b", this, "readState"); + + main.addComponent(b); + + + + main.addComponent(up); + l = new Label("Idle"); + main.addComponent(l); + + pi.setVisible(false); + pi.setPollingInterval(1000); + main.addComponent(pi); + + status.setVisible(false); + main.addComponent(status); + + + Button restart = new Button("R"); + restart.addListener(new Button.ClickListener() { + + public void buttonClick(ClickEvent event) { + getApplication().close(); + } + }); + main.addComponent(restart); + + + } + + public void readState() { + StringBuffer sb = new StringBuffer(); + + if (up.isUploading()) { + sb.append("Uploading..."); + sb.append(up.getBytesRead()); + sb.append("/"); + sb.append(up.getUploadSize()); + sb.append(" "); + sb.append(Math.round(100 * up.getBytesRead() + / (double) up.getUploadSize())); + sb.append("%"); + } else { + sb.append("Idle"); + } + l.setValue(sb.toString()); + } + + public void uploadFinished(FinishedEvent event) { + status.removeAllComponents(); + if (buffer.getStream() == null) + status.addComponent(new Label( + "Upload finished, but output buffer is null!!")); + else { + status + .addComponent(new Label("Name: " + + event.getFilename(), Label.CONTENT_XHTML)); + status.addComponent(new Label("Mimetype: " + + event.getMIMEType(), Label.CONTENT_XHTML)); + status.addComponent(new Label("Size: " + event.getLength() + + " bytes.", Label.CONTENT_XHTML)); + + status.addComponent(new Link("Download " + buffer.getFileName(), + new StreamResource(buffer, buffer.getFileName(), + getApplication()))); + + status.setVisible(true); + } + } + + public class Buffer implements StreamResource.StreamSource, Upload.Receiver { + ByteArrayOutputStream outputBuffer = null; + + String mimeType; + + String fileName; + + public Buffer() { + + } + + public InputStream getStream() { + if (outputBuffer == null) + return null; + return new ByteArrayInputStream(outputBuffer.toByteArray()); + } + + /** + * @see com.itmill.toolkit.ui.Upload.Receiver#receiveUpload(String, + * String) + */ + public OutputStream receiveUpload(String filename, String MIMEType) { + fileName = filename; + mimeType = MIMEType; + outputBuffer = new ByteArrayOutputStream(); + return outputBuffer; + } + + /** + * Returns the fileName. + * + * @return String + */ + public String getFileName() { + return fileName; + } + + /** + * Returns the mimeType. + * + * @return String + */ + public String getMimeType() { + return mimeType; + } + + } + + public void uploadFailed(FailedEvent event) { + System.out.println(event); + + System.out.println(event.getSource()); + + } + + public void uploadSucceeded(SucceededEvent event) { + pi.setVisible(false); + l.setValue("Finished upload, idle"); + System.out.println(event); + } + + public void updateProgress(long readBytes, long contentLenght) { + pi.setValue(new Float(readBytes/(float)contentLenght)); + } + + public void uploadStarted(StartedEvent event) { + pi.setVisible(true); + l.setValue("Started uploading file " + event.getFilename()); + } + +} diff --git a/src/com/itmill/toolkit/ui/ProgressIndicator.java b/src/com/itmill/toolkit/ui/ProgressIndicator.java index 8f90d33ae2..5a82fe441c 100644 --- a/src/com/itmill/toolkit/ui/ProgressIndicator.java +++ b/src/com/itmill/toolkit/ui/ProgressIndicator.java @@ -45,7 +45,7 @@ import com.itmill.toolkit.terminal.PaintTarget; * @author IT Mill Ltd. * @version * @VERSION@ - * @since 3.1 + * @since 4 */ public class ProgressIndicator extends AbstractField implements Property, Property.Viewer, Property.ValueChangeListener { diff --git a/src/com/itmill/toolkit/ui/Upload.java b/src/com/itmill/toolkit/ui/Upload.java index 68276eab7a..ffa23f3e2b 100644 --- a/src/com/itmill/toolkit/ui/Upload.java +++ b/src/com/itmill/toolkit/ui/Upload.java @@ -71,6 +71,18 @@ public class Upload extends AbstractComponent implements Component.Focusable { private long focusableId = -1; + private boolean isUploading; + + private long contentLength = -1; + + private int totalBytes; + + /** + * ProgressListener to which information about progress is sent during + * upload + */ + private ProgressListener progressListener; + /* TODO: Add a default constructor, receive to temp file. */ /** @@ -95,25 +107,16 @@ public class Upload extends AbstractComponent implements Component.Focusable { return "upload"; } - /** - * Invoked when the value of a variable has changed. - * - * @see com.itmill.toolkit.ui.AbstractComponent#changeVariables(java.lang.Object, - * java.util.Map) - */ - public void changeVariables(Object source, Map variables) { - - // Checks the variable name - if (!variables.containsKey("stream")) - return; - - // Gets the upload stream - UploadStream upload = (UploadStream) variables.get("stream"); + public void receiveUpload(UploadStream upload) { + if (!isUploading) + throw new IllegalStateException("uploading not started"); // Gets file properties String filename = upload.getContentName(); String type = upload.getContentType(); + fireStarted(filename, type); + // Gets the output target stream OutputStream out = receiver.receiveUpload(filename, type); if (out == null) @@ -121,32 +124,53 @@ public class Upload extends AbstractComponent implements Component.Focusable { "Error getting outputstream from upload receiver"); InputStream in = upload.getStream(); + if (null == in) { // No file, for instance non-existent filename in html upload fireUploadInterrupted(filename, type, 0); + endUpload(); return; } + byte buffer[] = new byte[BUFFER_SIZE]; int bytesRead = 0; - long totalBytes = 0; + totalBytes = 0; try { while ((bytesRead = in.read(buffer)) > 0) { out.write(buffer, 0, bytesRead); totalBytes += bytesRead; + if (progressListener != null && contentLength > 0) { + // update progress if listener set and contentLength + // received + progressListener.updateProgress(totalBytes, contentLength); + } } - // Download successfull + // upload successful out.close(); fireUploadSuccess(filename, type, totalBytes); + endUpload(); requestRepaint(); } catch (IOException e) { // Download interrupted fireUploadInterrupted(filename, type, totalBytes); + endUpload(); } } + /** + * Invoked when the value of a variable has changed. + * + * @see com.itmill.toolkit.ui.AbstractComponent#changeVariables(java.lang.Object, + * java.util.Map) + */ + public void changeVariables(Object source, Map variables) { + // NOP + + } + /** * Paints the content of this component. * @@ -164,6 +188,10 @@ public class Upload extends AbstractComponent implements Component.Focusable { if (this.tabIndex >= 0) target.addAttribute("tabindex", this.tabIndex); + target.addAttribute("state", isUploading); + + target.addVariable(this, "fake", true); + target.addUploadStreamVariable(this, "stream"); } @@ -198,12 +226,16 @@ public class Upload extends AbstractComponent implements Component.Focusable { private static final Method UPLOAD_SUCCEEDED_METHOD; + private static final Method UPLOAD_STARTED_METHOD; + static { try { UPLOAD_FINISHED_METHOD = FinishedListener.class.getDeclaredMethod( "uploadFinished", new Class[] { FinishedEvent.class }); UPLOAD_FAILED_METHOD = FailedListener.class.getDeclaredMethod( "uploadFailed", new Class[] { FailedEvent.class }); + UPLOAD_STARTED_METHOD = StartedListener.class.getDeclaredMethod( + "uploadStarted", new Class[] { StartedEvent.class }); UPLOAD_SUCCEEDED_METHOD = SucceededListener.class .getDeclaredMethod("uploadSucceeded", new Class[] { SucceededEvent.class }); @@ -282,21 +314,21 @@ public class Upload extends AbstractComponent implements Component.Focusable { } /** - * Gets the length of the file. + * Gets the MIME Type of the file. * - * @return the length. + * @return the MIME type. */ - public long getLength() { - return length; + public String getMIMEType() { + return type; } /** - * Gets the MIME Type of the file. + * Gets the length of the file. * - * @return the MIME type. + * @return the length. */ - public String getMIMEType() { - return type; + public long getLength() { + return length; } } @@ -360,6 +392,85 @@ public class Upload extends AbstractComponent implements Component.Focusable { } + /** + * Upload.Started event is sent when the upload is started to received. + * + * @author IT Mill Ltd. + * @version + * @VERSION@ + * @since 5.0 + */ + public class StartedEvent extends Component.Event { + + /** + * Serial generated by eclipse. + */ + private static final long serialVersionUID = -3984393770487403525L; + private String filename; + private String type; + + /** + * + * @param source + * @param filename + * @param MIMEType + * @param length + */ + public StartedEvent(Upload source, String filename, String MIMEType) { + super(source); + this.filename = filename; + this.type = MIMEType; + } + + /** + * Uploads where the event occurred. + * + * @return the Source of the event. + */ + public Upload getUpload() { + return (Upload) getSource(); + } + + /** + * Gets the file name. + * + * @return the filename. + */ + public String getFilename() { + return filename; + } + + /** + * Gets the MIME Type of the file. + * + * @return the MIME type. + */ + public String getMIMEType() { + return type; + } + + + } + + /** + * Receives the events when the upload starts. + * + * @author IT Mill Ltd. + * @version + * @VERSION@ + * @since 5.0 + */ + public interface StartedListener { + + /** + * Upload has started. + * + * @param event + * the Upload started event. + */ + public void uploadStarted(StartedEvent event); + } + /** * Receives the events when the uploads are ready. * @@ -417,6 +528,26 @@ public class Upload extends AbstractComponent implements Component.Focusable { public void uploadSucceeded(SucceededEvent event); } + /** + * Adds the upload started event listener. + * + * @param listener + * the Listener to be added. + */ + public void addListener(StartedListener listener) { + addListener(StartedEvent.class, listener, UPLOAD_STARTED_METHOD); + } + + /** + * Removes the upload started event listener. + * + * @param listener + * the Listener to be removed. + */ + public void removeListener(StartedListener listener) { + removeListener(FinishedEvent.class, listener, UPLOAD_STARTED_METHOD); + } + /** * Adds the upload received event listener. * @@ -477,6 +608,17 @@ public class Upload extends AbstractComponent implements Component.Focusable { removeListener(SucceededEvent.class, listener, UPLOAD_SUCCEEDED_METHOD); } + /** + * Emit upload received event. + * + * @param filename + * @param MIMEType + * @param length + */ + protected void fireStarted(String filename, String MIMEType) { + fireEvent(new Upload.StartedEvent(this, filename, MIMEType)); + } + /** * Emit upload received event. * @@ -572,4 +714,77 @@ public class Upload extends AbstractComponent implements Component.Focusable { return this.focusableId; } + /** + * 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. + */ + public void startUpload() { + if (isUploading) + throw new IllegalStateException("uploading already started"); + isUploading = true; + } + + /** + * Go into state where new uploading can begin. + */ + public void endUpload() { + isUploading = false; + contentLength = -1; + } + + public boolean isUploading() { + return isUploading; + } + + /** + * Gets read bytes of the file currently being uploaded. + * + * @return bytes + */ + public long getBytesRead() { + return totalBytes; + } + + /** + * Returns size of file currently being uploaded. Value sane only during + * upload. + * + * @return size in bytes + */ + public long getUploadSize() { + return contentLength; + } + + /** + * Sets listener to track progress of upload. + * + * @param progressListener + */ + public void setProgressListener(ProgressListener progressListener) { + this.progressListener = progressListener; + } + + /** + * ProgressListener receives events to track progress of upload. + */ + public interface ProgressListener { + /** + * Updates progress to listener + * + * @param readBytes + * bytes transferred + * @param contentLength + * total size of file currently being uploaded, -1 if unknown + */ + public void updateProgress(long readBytes, long contentLength); + } }