Download a file programatically

Hello,

I want to realize a scenario, that works something like that (pseudo code):

[code]
final Button button = new Button( “Print”, new ClickListener()
{
@Override
public void buttonClick( final ClickEvent pEvent )
{
final String fileName = createPdfDocument(); // RP-Call to generate a PDF file

    if ( isModusDirect() )
    {
        // Download the file to users computer
    }
    else if ( isModusMail() )
    {
        sendFileViaMail( fileName ); // RP-Call to send files via eMail
    }
    else if ( isModusFax() )
    {
        sendFileViaFax( fileName ); // RP-Call to send files via Fax
    }
}

} );
[/code]To download files I only found the
FileDownloader
. It works with extension of a button for example. But that does not fit my needs very well, or? Only in case
isModusDirect()
I want to downlad a file, in other two cases I only want to make a RPC with the filename. How should I realize that? Are there more possibilities to download files? Or can I create a second
Button
, which is not in my layout, that is extended by a
FileDownloader
and call
click()
on that button? Or should I work with a hidden second
Button
?
Second question is how to set the filename dynamically in the
FileDownloader
? Because every call of
createPdfDocument()
creates a new document with different filenames so I know the filename only after click on the
Button
and not before.But if I want to use the
FileDownloader
I have to know the filename before. Therefore I found the
OnDemandFileDownloader
. In

OnDemandStreamResource.getFilename()
I could call my
createPdfDocument()
but how can I be sure, that the method is only called once for one download? I could create a member which holds the filename in
OnDemandStreamResource
and
createPdfDocument()
is only called when the member is still
null
. But what is the right moment to set the member
null
again? Can I do it in/after

@Override 
public boolean handleConnectorRequest (VaadinRequest request, VaadinResponse response, String path) 
throws IOException 
{
    getResource().setFilename(onDemandStreamResource.getFilename()); 
    boolean bool = super.handleConnectorRequest(request, response, path); 
    // set filename member to null
    return bool;
}

or is it needed afterwards, too? What would be the best implementation for
getInputStream()
from the
StreamSource
interface?

One additional question: Is it possible to download more than one file at once? How does it works?
If I use the OnDemandFileDownloader and override method handleConnectorRequest() can I write something like this:

@Override
public boolean handleConnectorRequest (VaadinRequest request, VaadinResponse response, String path)
throws IOException
{
final String path1 = onDemandStreamResource.getPath1();
final String name1 = onDemandStreamResource.getFilename1();
    getResource().setFilename(name1);
    boolean bool = super.handleConnectorRequest(request, response, path1);
   
if ( !bool ) {
return false;
}

final String path2 = onDemandStreamResource.getPath2();
final String name2 = onDemandStreamResource.getFilename2();
    getResource().setFilename(name2);
    boolean bool = super.handleConnectorRequest(request, response, path2);

...
return bool;
}

hihi,

try this …

Button dl = new Button(“download foobar”);
dl.extend(new FileDownloader(new ConnectorResource() {
private final String filename = String.format(“foobar-%s.png”, new SimpleDateFormat(“yyyy-MM-dd-HH-mm”).format(new Date()));

  @Override
  public String getFilename() {
    return filename;
  }

  @Override
  public String getMIMEType() {
    return MediaType.PNG.toString();
  }

  @Override
  public DownloadStream getStream() {
    try {
      // generate data .... => inputstream
      InputStream data  = ....
      final DownloadStream stream = new DownloadStream(data, getMIMEType(), filename);
      stream.setParameter("Content-Disposition", "attachment;filename=" + filename);
      // This magic incantation should prevent anyone from caching the data
      stream.setParameter("Cache-Control", "private,no-cache,no-store");
      // In theory <=0 disables caching. In practice Chrome, Safari (and, apparently, IE) all ignore <=0. Set to 1s
      stream.setCacheTime(1000);
      return stream;
    } catch (final IOException e) {
      LOGGER.error("Can't download foobar", e);
    }
    return null;
  }
});

kind regards
Andreas


http://www.ahoehma.de

I found a solution for my problem :slight_smile: It is a little bit tricky, but it works. I only give a short overview how my solution works, because it is a lot of code. If you have any detailed questions about it, please let me know.

The problem is, I want to have a download button, which downloads files when I click on it. But the files have to be generated first during the click. Before the click, I do not know the name of the files and the number of files.

That is my solution:
First of all, it is possible to place more than one extension to the button or what ever.

I have a button where I register a fixed number of
OnDemandFileDownloader
(for example 10 when I know, that there will never be more than 10 files generated at once). When the first
OnDemandFileDownloader
gets active, it makes the rpcall to generate the files and the return value (names of the generated files) are stored in the button, so all
OnDemandFileDownloader
have access to them. Afterwards it fetches the first file name and tries to download the file. The second, third, …
OnDemandFileDownloader
know, that the first
OnDemandFileDownloader
has already made the rpcall, so they only fetch the next file name and try to download it. If no more file names are left, nothing happens. The last
OnDemandFileDownloader
resets everything so when the next click comes and the first
OnDemandFileDownloader
(it must not be the same as it was in the first click) starts it knows, it must first do the rpcall, store the file names in the button and so on.
The most complicated thing is the management, who is the first, last and so on.
I also realized a solution with
BrowserWindowOpener
. I just implemented a
OnDemandBrowserWindowOpener
. It is a 1:1 copy of the
OnDemandFileDownloader
, but you have to use
url
instead of
dl
in
getRessource()
. The rest is exactly the same.

Could you post some code for your solution. I have the same problem. The parts of OnDemandFileDownloader that differ from the version on the Vaadin wiki and how to instantiate these OnDemandFileDownloaders would be most helpful.

Sorry for the late answer, last weeks I was on vacations. Here is the code, you are looking for (it is mostly in german, if you have any problems with it, please let me know):

OnDemandFileDownloader (inner class in DateiKnopf): [code]
private final class OnDemandFileDownloader
extends FileDownloader
{
private final OnDemandStreamResource vRessource;

    public OnDemandFileDownloader (
            final OnDemandStreamResource pRessource )
    {
        super( new StreamResource( pRessource, "" ) );
        vRessource = pRessource;
    }

    @Override
    public boolean handleConnectorRequest(
            final VaadinRequest pRequest,
            final VaadinResponse pResponse,
            final String pPath )
    throws IOException
    {
        if ( !getKnopfStatus().isNormal() )
        {
            // Richtigerweise 'false', dies bewirkt allerdings auch nur einen Eintrag
            // im Logger und ein anschließendes 'return true;'
            // siehe ConnectorResourceHandler.error()
            return true;
        }

        final String dateiname = vRessource.getDateiname();
        if ( dateiname == null )
        {
            ressourceAusgewertet( vRessource );
            // Richtigerweise 'false', dies bewirkt allerdings auch nur einen Eintrag
            // im Logger und ein anschließendes 'return true;'
            // siehe ConnectorResourceHandler.error()
            return true;
        }

        getResource().setFilename( dateiname );

        final boolean ergebnis = super.handleConnectorRequest( pRequest, pResponse, pPath );

        ressourceAusgewertet( vRessource );

        return ergebnis;
    }

    private StreamResource getResource()
    {
        return (StreamResource) this.getResource( "dl" );
    }
}

[/code]

OnDemandStreamResource (inner class in DateiKnopf): [code]
private static interface OnDemandStreamResource
extends StreamSource
{
String getKomplettenPfad();

    String getDateiname();

    void zuruecksetzen();
}

[/code]BasisOnDemandStreamResource (inner class in DateiKnopf):[code]
private class BasisOnDemandStreamResource
implements OnDemandStreamResource
{
boolean vPfadGelesen;
String vPfad;

    @Override
    public InputStream getStream()
    {
        try
        {
            return new URL( getKomplettenPfad() ).openStream();
        }
        catch ( final IOException ioe )
        {
            throw new RuntimeException( ioe );
        }
    }

    @Override
    public String getKomplettenPfad()
    {
        if ( !vPfadGelesen )
        {
            vPfadGelesen = true;
            vPfad = getNaechstenKomplettPfad( this );
        }

        return vPfad;
    }

    @Override
    public String getDateiname()
    {
        final String pfad = getKomplettenPfad();

        if ( pfad == null ) {
            return null;
        }

        final String[] teile = pfad.split( "/" );

        return teile[ teile.length - 1 ]

;
}

    @Override
    public void zuruecksetzen()
    {
        vPfadGelesen = false;
        vPfad = null;
    }
}

[/code]DateiAktion:

[code]
public interface DateiAktion
extends Serializable
{
/**
* Liefert eine Liste von Dateinamen inklusive Pfad
* relativ zu {@link #getWebServiceAdresse()}.
*
* @return Liste von Dateinamen inkl. relativem Pfad
*/
public List getDateienMitPfad();

/**
 * Liefert die Basis-Adresse, unter der die Dateien aus
 * {@link DateiAktion#getDateienMitPfad()} zu finden sind.
 * In der Regel ist dies die Adresse des WebService ohne
 * {@code services/CscRPC2}, z.B. {@code http://localhost:1978/}
 *
 * @return Die Basis-Adresse
 */
public URL getWebServiceAdresse();

}
[/code]DateiKnopf:

class DateiKnopf
{
    private final Button vKnopf;

    private DateiAktion vAktion;
    private String [] vKomplettenPfade;
    private List<OnDemandStreamResource> vAusgefuehrteRessourcenLeser;
    private List<OnDemandStreamResource> vBeendeteRessourcenLeser;

    private static final int C_MAX_DATEIEN = 30;

    public DateiKnopf(
            final String pLabel,
            final DateiAktion pAktion )
    {
        vKnopf = new Button( pLabel );

        vAktion = pAktion;
        erzeugeExtensions();
    }

    private void erzeugeExtensions()
    {
        for ( int i = 0; i < C_MAX_DATEIEN; i++ )
        {
            final OnDemandFileDownloader lader =
                    new OnDemandFileDownloader( new BasisOnDemandStreamResource() );
            lader.extend( vKnopf );
        }
    }

    private synchronized String getNaechstenKomplettPfad(
            final OnDemandStreamResource pAnfrageRessource )
    {
        if ( vAktion == null ) {
            return null;
        }

        // Es wurde ein Reset durchgeführt (Dateien sind neu zu lesen)
        if ( vKomplettenPfade == null && vAusgefuehrteRessourcenLeser == null )
        {
            vKomplettenPfade = new String[ C_MAX_DATEIEN ]
;
            vAusgefuehrteRessourcenLeser = new ArrayList<>( C_MAX_DATEIEN );
            vBeendeteRessourcenLeser = new ArrayList<>( C_MAX_DATEIEN );

            final List<String> pfade = vAktion.getDateienMitPfad();
            final URL webServiceAdresse = vAktion.getWebServiceAdresse();

            if ( pfade.size() > C_MAX_DATEIEN ) {
                loggeWarnung( "Es wurden mehr Dateien ermittelt, als dem Nutzer zur Verfügung gestellt werden können!");
            }

            int i = 0;
            for ( final String pfad : pfade )
            {
                if ( i == C_MAX_DATEIEN ) {
                    break;
                }

                vKomplettenPfade[i]
 = (webServiceAdresse + pfad).replace( "\\", "/" );
                i++;
            }
        }
        
        if(vAusgefuehrteRessourcenLeser == null)
            return null;
        
        // aktuellen Pfad auslesen und anfragende Ressource merken
        final String pfad = vKomplettenPfade[vAusgefuehrteRessourcenLeser.size()]
;
        vAusgefuehrteRessourcenLeser.add( pAnfrageRessource );

        return pfad;
    }

    private synchronized void ressourceAusgewertet(
            final OnDemandStreamResource pAugewerteteRessource )
    {
        vBeendeteRessourcenLeser.add( pAugewerteteRessource );

        // War dies die letzte anfragende Ressource?
        if ( vBeendeteRessourcenLeser.size() == C_MAX_DATEIEN )
        {
            for ( final OnDemandStreamResource ressource : vBeendeteRessourcenLeser )
            {
                ressource.zuruecksetzen();
            }
            vAusgefuehrteRessourcenLeser.clear();
            vAusgefuehrteRessourcenLeser = null;

            vBeendeteRessourcenLeser.clear();
            vBeendeteRessourcenLeser = null;

            vKomplettenPfade = null;
        }
    }

    public AbstractComponent getView()
    {
        return vKnopf;
    }
}