3

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?

zmerr
  • 534
  • 3
  • 18
Dave Sims
  • 63
  • 7

2 Answers2

0

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 :)

Community
  • 1
  • 1
Pere Villega
  • 16,429
  • 5
  • 63
  • 100
  • a critical fix for uploading files over websockets is coming soon... https://github.com/playframework/play1/pull/709 – Dean Hiller Dec 16 '13 at 14:28
0

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..

hgoz
  • 641
  • 6
  • 18
  • except ArchivedEventStream will DROP packets on buffer maxing out....a poll request to fix the EventStream only for websockets is here......someone would need to speak up if they want ArchivedEventStream to not drop packets like it does today... https://github.com/playframework/play1/pull/709 – Dean Hiller Dec 16 '13 at 14:29