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?
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 :)
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' /}
download
<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..
Related
I am developing a web application in java. It has a file that is read on every page request, but I have to think about changing it (which is rare). You need to come up with something to make everything work as quickly as possible. Any advice?
You can use a sort of copy_on_change.
If your file is /path/myFile_v1.txt
Write a code similar to
private AtomicInteger version=1;
public String getFilePath() {
return "/path/myFile_v"+version.get()+".txt"
}
public void synchronized makeAChange() {
// create a new copy of the file with some changes
version.incrementAndGet();
}
you can remove old copies after some time or remove version-2 each time.
Reading threads are not blocked while you make changes.
I want to add an upload button to my web application. In the HTML template, I added:
<link rel="import" href="../../../bower_components/vaadin-upload/src/vaadin-upload.html">
<h2 id="header-Upload">Upload</h2>
<vaadin-upload nodrop/>
I now want to receive the stream in my backend Vaadin process. This stream can be then inserted in the database.
The documentation on https://vaadin.com/components/vaadin-upload/html-examples/upload-basic-demos doesn't provide this information.
I believe I should somehow link a StreamReceiver to the <vaadin-upload> in question, but I am not sure how to do that.
I am using Vaadin Flow (version 12).
Additional information
I tried the following:
In HTML:
<vaadin-upload id="upload" nodrop/>
In Java:
public class MyView extends PolymerTemplate<Model> {
private final MemoryBuffer buffer = new MemoryBuffer();
#Id("upload")
private final Upload upload = new Upload(buffer);
public MyView() {
upload.addSucceededListener(event -> {
System.out.println(event.getFileName());
System.out.println(buffer.getInputStream());
});
}
}
When I upload a file, I get the following exception:
[qtp162821120-20] ERROR com.vaadin.flow.server.DefaultErrorHandler -
com.vaadin.flow.server.UploadException: Upload failed
at com.vaadin.flow.server.communication.StreamReceiverHandler.streamToReceiver(StreamReceiverHandler.java:429)
Caused by: java.lang.IllegalStateException: Upload cannot be performed without a receiver set
at com.vaadin.flow.component.upload.Upload$DefaultStreamVariable.getOutputStream(Upload.java:581)
at com.vaadin.flow.server.communication.StreamReceiverHandler.streamToReceiver(StreamReceiverHandler.java:358)
massive Edit: The actual solution to your problem was that you instantiated the Upload in your java class, when you should let Vaadin do this (because of the polymer template) and refer to this upload component using an identifier both in the html template (<vaadin-upload id="upload" nodrop/>) as well as in the java code (#Id("upload") private Upload upload;). You can now set the receiver of the upload in the java class using upload.setReceiver(buffer);.
public class MyView implements PolymerTemplate<Model> {
#Id("upload") // com.vaadin.flow.component.polymertemplate
private Upload upload; // NO INSTANTIATION!
private private final MemoryBuffer buffer = new MemoryBuffer();
public MyView (){
upload.setReceiver(buffer);
upload.addSucceededListener(event -> {
System.out.println(event.getFileName());
System.out.println(buffer.getInputStream());
});
}
}
My initial answer was using an unnecessary alternative approach that was not related to your problem (I thought so at first), but helped finding the real solution.
The best solution depends a bit about your context, mainly how does your backend accept the data? Does it accept inputstream where it can read the data, does it provide you an outputstream where to write it or is there a simple byte[] where to buffer the content.
If you are using Binder in your form, you could use ByteArrayUploadField from Viritin add-on, which directly binds to byte[] type that you probably have in your domain object.
I recently wrote an article that explains the functionality of Vaadin Upload component. Check that for more details.
I have been battling this code the whole day without luck. I started by following this code sample from Google.
The problem is that the folder gets created successfully but inside onResult(), I always get a DriveId or resourceId that is invalid or incomplete. That means I cannot create a file inside the folder I created. Here is the code I am using:
public class CreateFolderActivity extends BaseDemoActivity {
#Override
public void onConnected(Bundle connectionHint) {
super.onConnected(connectionHint);
MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
.setTitle("MyAppFolder").build();
Drive.DriveApi.getRootFolder(getGoogleApiClient()).createFolder(
getGoogleApiClient(), changeSet).setResultCallback(callback);
}
final ResultCallback<DriveFolderResult> callback = new ResultCallback<DriveFolderResult>() {
#Override
public void onResult(DriveFolderResult result) {
if (!result.getStatus().isSuccess()) {
showMessage("Error while trying to create the folder");
return;
}
// this value is always invalid with ending == operators
Log.d("DRIVEID", "Created a folder: " + result.getDriveFolder().getDriveId());
}
};
}
Whenever I run this code, I get the following id which appears incomplete:
CAESABi2VyCCzdWOuVMoAQ==
I don't know what is happening here!
I have Google'd around and read of adding listeners to listen for completion events but none of them seem to work.
I have seen nearly similar questions on SO on this but none of them work for me.
I manually copied the FolderId through my browser after the app created it and then pasted to my android code and the app created a file successfully. But this is not how things should work.
Am I suppose to wait for the sync to complete and if so, how?
Thank you in advance!
The answer to your problem can probably be found here. The DriveId you're getting is OK, but you should not handle it directly. It is a 'preliminary' DriveId that changes after the object has been committed (again, see SO 22874657). You can test it comparing DriveId you're getting vs. DriveId you'll get in 'onCompletion(CompletionEvent event)'.
This is just one of the side effects of GDAA's logic, shielding you from on-line / off-line network state resulting in unpredictable delays. You just have to rely on callbacks.
But I am surprised that you can't use this 'preliminary' DriveId (in case of a folder) immediately as a parent of another object (folder/file). I have never experienced it, passing the 'preliminary' DriveId immediately to another GDAA method.
It is different in case of the ResourceId. That one is secondary in the GDAA and is used only if you go outside of the device. It is not known to the GDAA until the object is committed (uploaded).
I used similar logic (creating folder / file tree) in this demo (see MainActivity.createTree() method). You're welcome to dig in it.
There is a related problem discussed in SO 34318220.
Good Luck
I am having some strange trouble with the method await(Future future) of the Controller.
Whenever I add an await line anywhere in my code, some GenericModels which have nothing to do with where I placed await, start loading incorrectly and I can not access to any of their attributes.
The wierdest thing is that if I change something in another completely different java file anywhere in the project, play will try to recompile I guess and in that moment it starts working perfectly, until I clean tmp again.
When you use await in a controller it does bytecode enhancement to break a single method into two threads. This is pretty cool, but definitely one of the 'black magic' tricks of Play1. But, this is one place where Play often acts weird and requires a restart (or as you found, some code changing) - the other place it can act strange is when you change a Model class.
http://www.playframework.com/documentation/1.2.5/asynchronous#SuspendingHTTPrequests
To make it easier to deal with asynchronous code we have introduced
continuations. Continuations allow your code to be suspended and
resumed transparently. So you write your code in a very imperative
way, as:
public static void computeSomething() {
Promise delayedResult = veryLongComputation(…);
String result = await(delayedResult);
render(result); }
In fact here, your code will be executed in 2 steps, in 2 different hreads. But as you see it, it’s very
transparent for your application code.
Using await(…) and continuations, you could write a loop:
public static void loopWithoutBlocking() {
for(int i=0; i<=10; i++) {
Logger.info(i);
await("1s");
}
renderText("Loop finished"); }
And using only 1 thread (which is the default in development mode) to process requests, Play is able to
run concurrently these loops for several requests at the same time.
To respond to your comment:
public static void generatePDF(Long reportId) {
Promise<InputStream> pdf = new ReportAsPDFJob(report).now();
InputStream pdfStream = await(pdf);
renderBinary(pdfStream);
and ReportAsPDFJob is simply a play Job class with doJobWithResult overridden - so it returns the object. See http://www.playframework.com/documentation/1.2.5/jobs for more on jobs.
Calling job.now() returns a future/promise, which you can use like this: await(job.now())
I need to transfer files to my web server for processing and I'd like to do it in a generic way if possible.
I need to be able to transfer files from the following protocols at a minimum (with more to follow eventually):
HTTP
FTP
SCP
I'd really like to be able to send files to SMTP also
So my question, is there a toolkit available that does this already? If so, it must be open source as this is part of an open source project.
If there isn't a toolkit that already does this, what is the best way to structure an interface that will handle most file transfers?
I've thought about something like this:
public interface FileTransfer {
public void connect(URL url, String userid, String password);
public void disconnect();
public void getFile(String sourceFile, File destFile);
public void putFile(File sourceFile, File destFile);
}
And then a Factory that takes the source URL or protocol and instantiates the correct file handler.
Apache commons VFS speaks to this problem, although a quick check didn't show that it will do SCP or SMTP. Commons NET does SMTP, but I don't know that you could get the common interface out of the box. For SCP, here are some possibilities.
The bottom line seems to be to check out the VFS implementation and see if it does something for you, perhaps you can extend it for different protocols. If it isn't appropriate, regarding your interface, you are probably going to want all remote file references to be Strings rather than File objects, and specifically a string representing a URI pointing to the remote location and telling you what protocol to use.
I'm working at a problem very similar to yours, I couldn't find any open source solution so I'm trying to sketch a solution myself. This is what I've come up with.
I think you should represent inputSources and outputSources as different things, like
public interface Input{
abstract InputStream getFileInputStream();
abstract String getStreamId();
}
//You can have differen implementation of this interface (1 for ftp, 1 for local files, 1 for Blob on db etc)
public interface Output{
abstract OutputStream getOutputStream();
abstract String getStreamId();
}
//You can have differen implementation of this interface (1 for ftp, 1 for local files, 1 for mailing the file etc)
Then you should have a Movement to describe which input should go to which output.
class Movement{
String inputId;
String outputId;
}
A class to describe the list of Movement to make.
class MovementDescriptor{
public addMovement(Movement a);
public Movement[] getAllMovements();
}
And then a class to perform the work itself.
class FileMover{
HashMap<String,Input> inputRegistry;
HashMap<String,Output> outputRegistry;
addInputToRegistry(Input a ){
inputRegistry.put(a.getId(),a);
}
addOutputToRegistry(Output a){
outputRegistry.put(a.getId(),a);
}
transferFiles(MovementDescriptor movementDescriptor){
Movement[] movements =movementDescriptor.getAllMovements();
foreach (Movement movement: movements){
//get the input Id
//find it in the registry and retrieve the associated InputStream
//get the output Id
//find it in the registry and retrieve the associated OutputStream
//copy the stream from the input to the output (you may want to use a temporary file in between)
}
}
}
The code that would use this would operate like this:
FileMover fm=new FileMover();
//Register your sources and your destinations
fm.addInputToRegistry(input);
fm.addOutputToRegistry(output)
// each time you have to make a movement create a MovementDescriptor and call
fm.transferFiles(movementDescriptor)
If you would like to exchange by mail our views on the subject, just send me an e mail at (my nickname)#gmail dot com.
NOTE: The code is just a sketch :-)
I think JSch implements SCP, so that covers that one.
please make use of JCraft . Open "sftp" channel and try that.