]> source.dussan.org Git - vaadin-framework.git/commitdiff
Streaming upload and client side implementation for upload + progressindicator
authorMatti Tahvonen <matti.tahvonen@itmill.com>
Thu, 27 Sep 2007 08:43:27 +0000 (08:43 +0000)
committerMatti Tahvonen <matti.tahvonen@itmill.com>
Thu, 27 Sep 2007 08:43:27 +0000 (08:43 +0000)
svn changeset:2381/svn branch:trunk

src/com/itmill/toolkit/terminal/gwt/client/ApplicationConnection.java
src/com/itmill/toolkit/terminal/gwt/client/DefaultWidgetSet.java
src/com/itmill/toolkit/terminal/gwt/client/ui/IProgressIndicator.java [new file with mode: 0644]
src/com/itmill/toolkit/terminal/gwt/client/ui/IUpload.java
src/com/itmill/toolkit/terminal/gwt/public/default/progressindicator/progressindicator.css [new file with mode: 0644]
src/com/itmill/toolkit/terminal/gwt/public/default/styles.css
src/com/itmill/toolkit/terminal/gwt/server/ApplicationServlet.java
src/com/itmill/toolkit/terminal/gwt/server/CommunicationManager.java
src/com/itmill/toolkit/tests/TestForUpload.java [new file with mode: 0644]
src/com/itmill/toolkit/ui/ProgressIndicator.java
src/com/itmill/toolkit/ui/Upload.java

index 9069bbe6d168db922cbd2b4d217cc816f1bd885a..8a5d55bfe124de46c4fd8811837551f186116612 100755 (executable)
@@ -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;
                                }-*/;
 
index d0ccf5d8004b6350383bc888b9cdfc6b4b94e99d..2f67f6fbf4983b4468f289499d8afdca7d8f892a 100644 (file)
@@ -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 (file)
index 0000000..d0a5b1c
--- /dev/null
@@ -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();
+               }
+               
+       }
+       
+}
index b1c17adf6645dabc04853cb6ae7107c3aef50c2b..b43293cdc1ed53c9c826e070070a65d80aa6a6d1 100644 (file)
@@ -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 (file)
index 0000000..cd12ada
--- /dev/null
@@ -0,0 +1,8 @@
+.i-progressindicator {
+       background: red;
+       height: 20px;
+}
+.i-progressindicator div {
+       background: green;
+       height:100%;
+}
\ No newline at end of file
index 0a2300e2c117cbdf06bd6b5f11b5a43e8fa49617..6b686274c01e8f566c45038f65b7057bddb518bd 100644 (file)
@@ -12,4 +12,5 @@
 @import "tree/tree.css";\r
 @import "splitpanel/splitpanel.css";\r
 @import "select/filterselect.css";\r
+@import "progressindicator/progressindicator.css";
 \r
index db0ff14bbf44efdaa619d1acdc832005987ee281..ef4d173542af9ff585860857f4586d5e508f41d0 100644 (file)
@@ -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)
index 5965562f1d47c462a40eb147e9ff8bf56204d46a..d0c9af00aa5aab2019a04d5f67b298cf7775c33c 100644 (file)
@@ -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("<html><body>download handled</body></html>");
+               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 (file)
index 0000000..a981ec2
--- /dev/null
@@ -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("<b>Name:</b> "
+                                                       + event.getFilename(), Label.CONTENT_XHTML));
+                       status.addComponent(new Label("<b>Mimetype:</b> "
+                                       + event.getMIMEType(), Label.CONTENT_XHTML));
+                       status.addComponent(new Label("<b>Size:</b> " + 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());
+       }
+
+}
index 8f90d33ae27e53aee9ab5385979658ed2e6219bc..5a82fe441ca927c07c0474d85c61da480b9df5ef 100644 (file)
@@ -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 {
index 68276eab7a20cb4dcc3d80b7b77c3c193e90defe..ffa23f3e2b15789d9142f90a95f32d3e4af19b81 100644 (file)
@@ -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);
+       }
 }