3
votes

I'm successfully using Play 1.2.4 to serve large binary file downloads to users using the renderBinary() method.

I'd like to have a hint of when the user actually completes the download. Generally speaking, I know this is somewhat possible as I've done it before. In an old version of my website, I wrote a simple servlet that served up binary file downloads. Once that servlet finished writing out the contents of the file, a notification was sent. Certainly not perfect, but useful nonetheless. In my testing, it did provide an indication of how long the user took to download a file.

Reviewing the Play source, I see that the play.mvc.results.RenderBinary class has a handy apply() method that I could use. I wrote my own version of RenderBinary so I could send the notification after the apply() method finished writing out the file contents.

The problem I found is that calls to response.out.write() obviously cache the outgoing bytes (via Netty?), so even though I am writing out several megabytes of data, the calls to play.mvc.Http.Response.out.write() complete in seconds, even though it takes the downloader a couple minutes to download the file.

I don't mind writing custom classes, although I'd prefer to use a stock Play 1.2.4 distribution.

Any ideas on how to get a notification of when the end of a file download is pushed out towards the user's browser?

2

2 Answers

0
votes

It seems this may help you, as it tackles a somehow similar problem:

Detect when browser receives file download

I'm not sure you'll eb able to do it via renderBinary nor an @After annotation in the controller. Some browser-side detection of the download and then a notification to the server (pinging the download's end) would work.

There may be an alternative: using WebSockets (streaming the file via the socket and then having teh client to answer) but it may be overkill for this :)

0
votes

you can use ArchivedEventStream.

first create a serializable ArcivedEventStream class..

public class Stream<String> extends ArchivedEventStream<String> implements Serializable{

   public Stream(int arg0) {
        super(arg0);
   }

}

then on your controller...

public static void downloadPage(){
    Stream<String> userStream = Cache.get(session.getId(),Stream.class);
    if( userStream == null){
        userStream = new Stream<String>(5);
        Cache.add(session.getId(), userStream);
    }
    render();
}

public static void download(){
    await(10000);// to provide some latency. actually no needed
    renderBinary(Play.getFile("yourfile!"));
}

public static void isDownloadFinished(){
    Stream<String> userStream = Cache.get(session.getId(),Stream.class);
    List<IndexedEvent<String>> list = await(userStream.nextEvents(0));
    renderJSON(list.get(0).data);

}

@After(only="download")
static void after(){
    Stream<String> userStream = Cache.get(session.getId(),Stream.class);
    userStream.publish("ok");
}

on your html...

#{extends 'main.html' /}

#{set title:'downloadPage' /}
<a href="download" target="_blank">download</a>

<script>
$(document).ready(function(){
$.ajax('/application/isDownloadFinished',{success:function(data){
    if(data){
        console.log("downloadFinished");    
    }

}});
});

</script>

when your download finished, the original page will retrieve the notification.

This code is just a sample. You could read the api of ArchivedEventStream and make your own implementation..