FileDownloader and ProgressBar

Hello,

We are using vaadin 7.1 with a FileDownloader component. The download function is very long and in the past (vaadin 7.0.5) we decided to show a ProgressIndicator. Now that we migrated to vaadin 7.1 the progressWindow is not show anymore We changed our code to use ProgressBar instead of ProgressIndicator but we have problems because the progressbar dialog (ProgressWindow) is not shown.

Is it right to create the ProgressBar in the createResource method ?

Is the ProgressWindow created correctly ?

The fileDownloader is as follows:


        StreamResource myResource = createResource();
        FileDownloader fileDownloader = new FileDownloader(myResource);
        fileDownloader.extend(downloadContacts);

The createResource is defined in this way:


private StreamResource createResource() {
        return new StreamResource(new StreamSource() {

            @Override
            public InputStream getStream() {
                ProgressBar pi = new ProgressBar();
                pi.setIndeterminate(false);
                pi.setEnabled(true);
                pi.setValue(0f);

                UI.getCurrent().setPollInterval(200);
                
                final ProgressWindow wdw = new ProgressWindow("Please wait", "Operation in progress", pi);
                
                wdw.show();
                
                MyVeryLongTaskRunnable task = new MyVeryLongTaskRunnable();
                Thread taskThread = new Thread(task);
                taskThread.start();
                
                try {
                    exportThread.join();
                } catch (InterruptedException e) {
                    ConsoleUtil.getLog().error(e.getMessage(), e);
                    return null;
                }
                UI.getCurrent().setPollInterval(-1);
                
                return new ByteArrayInputStream(export.baos.toByteArray());
                
            }
        }, "file.txt");
    }

ProgressWindow.java:


public class ProgressWindow extends Window{
    
    private static final long serialVersionUID = 161901606322111895L;
    
    public ProgressWindow(String title, String message, ProgressBar pi){
        setCaption(title);
        
        VerticalLayout root = new VerticalLayout();
        root.setMargin(true);
        root.setSpacing(true);
        root.setSizeFull();
        
        setModal(true);
        setClosable(false);
        
        Label lblTitle = new Label(message);
        lblTitle.setStyleName(Reindeer.LABEL_H2);

        root.addComponent(lblTitle);
        root.addComponent(pi);
        
        root.setComponentAlignment(pi, Alignment.MIDDLE_CENTER);
        root.setComponentAlignment(lblTitle, Alignment.MIDDLE_CENTER);
        
        setContent(root);
        
        setWidth(300, Unit.PIXELS);
        setHeight(100, Unit.PIXELS);
    }

    public void show() {
        UI.getCurrent().addWindow(this);
    }
    
    public void close(){
        UI.getCurrent().removeWindow(this);            
    }
    
}

So in 7.0.5 you were using the ProgressIndicator and everything worked (Window opens and Indicator works).
When changing to 7.1.0 and using a ProgressBar+Polling instead the window isn’t shown?
Is that right?
What happens if you try using the deprecated ProgressIndicator + Vaadin 7.1.0 ?

It seems you don’t give the UI changes made in
getStream()
a chance to be communicated to the client-side - the request handling thread blocks (and keeps the session locked!) until the worker thread is done. And even then the UI changes aren’t sent, as this is a resource request and not a UI request. Enabling polling on the server side doesn’t help because the client side isn’t notified that it should poll. And even if polling was enabled the whole time, the poll request would stall, waiting for the session lock to be released by the download thread.

A couple of options:

  1. Keep polling active (or use push) all the time and manually release the session lock while waiting the export thread to join; reacquire it after the join call returns.
  2. Use a regular button to start the export thread, show progress bar, enable polling etc. and return; at the end of the export thread Runnable, invoke UI.access() to show the actual download button (you need to pass the UI reference to the thread). Yes, this means the user has to click twice.

Edit: this is probably the best solution:

  • Add a regular ClickListener to the same button that has FileDownloader; in the listener enable polling, show progress bar etc. The mutual ordering of the click request and the download request isn’t well-defined, but it doesn’t matter as long as you release the lock while waiting for the export thread - which you have to do anyways in order not to block the polling requests.

Openend
#12264
for seeing what could be done to make this easier.

Oh, and if you have push enabled, you can do a manual UI.push() whenever you want and the changes are sent to the client immediately.

I know this Thread is fairly old, but this is still an issue for me and I’m using 7.4.2. The solution Johannes touches on briefly here (related to Session locking and getStream()) is expanded in the Trac ticket #12264 works BEAUTIFULLY!

For those (like me) to whom this solution is not obvious from this thread alone:

Briefly, use PipedInputStream and PipedOutputStream in conjunction - passing the PipedOutputStream to your method that does the “heavy lifting” of generating the file - and run the method in a separate thread (since the getStream() call runs within the scope of the locked session). Write to the PipedOutputStream, close it when you’re done, and VOILA!

Here is my code, adapted from Johannes’ solution:

FileDownloader opener = new FileDownloader( new StreamResource( new StreamSource() { @Override public InputStream getStream() { PipedInputStream is = new PipedInputStream(); try { final PipedOutputStream outputStream = new PipedOutputStream(is); new Thread() { public void run() { // The "heavy lifting" handler.handleFileExport(outputStream); } }.start(); } catch (IOException e) { log.error("Fatal error occurred!", e); } return is; } }, generateExportFileName(exportInfoCallback) )); opener.extend(exportButton); Thanks, Johannes.