How to send a variable from server to client to meteor?

I have a page with text input and a button. When I paste the youtube video link into the text box and click the button - upload the video to a local folder.

Problem: how can I send a link to a local copy of the downloaded video back to the client?

More general question: How to send a variable from server to client (this variable is temporary and will not be stored anywhere)?

The code I have right now is:

Client code

if (Meteor.isClient) { Path = new Meteor.Collection("path"); Meteor.subscribe("path"); Template.hello.events( { 'submit .form' : function() { var link = document.getElementById("youtube-url").value; Meteor.call('download', link); event.preventDefault(); } } ); } 

Server code (part of 'collection' does not work)

 if (Meteor.isServer) { Meteor.startup(function () { Meteor.methods({ download: function (link) { var youtubedl = Npm.require('youtube-dl'); var Fiber = Npm.require("fibers"); var dl = youtubedl.download(link, './videos'); // called when youtube-dl finishes dl.on('end', function(data) { console.log('\nDownload finished!'); Fiber(function() { Path = new Meteor.Collection("path"); Path.insert({path: './videos/' + data.filename}); }) }); } }); }); } 

Thanks!

+6
source share
4 answers

The answer to the question is divided into two parts: (a) processing of asynchronous functions inside the Meteor methods and (b) using the youtube-dl package.

Async functions inside Meteor methods

In Meteor methods, there are 2+ ways to work with asynchronous functions: using future and using wrapAsync . If you look at the sources of Meteor, you will see that wrapAsync itself uses the future : https://github.com/meteor/meteor/blob/master/packages/meteor/helpers.js#L90 . You can also use fibers directly, but not recommended .

The following are common examples of their use:

 'use strict'; if (Meteor.isClient) { Template.methods.events({ 'click #btnAsync' : function() { console.log('Meteor.call(asyncMethod)'); Meteor.call('asyncMethod', 1000, function(error, result) { if (error) { console.log('Meteor.call(asyncMethod): error:', error); } else { console.log('Meteor.call(asyncMethod): result:', result); } }); }, 'click #btnFuture' : function() { console.log('Meteor.call(futureMethod)'); Meteor.call('futureMethod', 1000, function(error, result) { if (error) { console.log('Meteor.call(futureMethod): error:', error); } else { console.log('Meteor.call(futureMethod): result:', result); } }); }, 'click #btnFiber' : function() { console.log('Meteor.call(fiberMethod)'); Meteor.call('fiberMethod', 1000, function(error, result) { if (error) { console.log('Meteor.call(fiberMethod): error:', error); } else { console.log('Meteor.call(fiberMethod): result:', result); } }); } }); } if (Meteor.isServer) { var demoFunction = function(duration, callback) { console.log('asyncDemoFunction: enter.'); setTimeout(function() { console.log('asyncDemoFunction: finish.'); callback(null, { result: 'this is result' }); }, duration); console.log('asyncDemoFunction: exit.'); }; var asyncDemoFunction = Meteor.wrapAsync(demoFunction); var futureDemoFunction = function(duration) { var Future = Npm.require('fibers/future'); var future = new Future(); demoFunction(duration, function(error, result) { if (error) { future.throw(error); } else { future.return(result); } }); return future.wait(); }; var fiberDemoFunction = function(duration) { var Fiber = Npm.require('fibers'); var fiber = Fiber.current; demoFunction(duration, function(error, result) { if (error) { fiber.throwInto(new Meteor.Error(error)); } else { fiber.run(result); } }); return Fiber.yield(); }; Meteor.methods({ asyncMethod: function (duration) { return asyncDemoFunction(duration); }, futureMethod: function (duration) { return futureDemoFunction(duration); }, fiberMethod: function (duration) { return fiberDemoFunction(duration); } }); } 

You can also look at Meteor.bindEnvironment() and future.resolver() for more complex cases.

Christian Fritz provided the correct template for using wrapAsync , however, within 2 years, from the moment the initial question was asked, the youtube-dl package API has changed.

Using the youtube-dl package

Due to changes in the API, if you run its code, the server throws an exception visible on its console:

 Exception while invoking method 'download' TypeError: Object function (videoUrl, args, options) { ... } has no method 'download' 

And Meteor returns an undefined value to the client:

 here is the path: undefined 

Below is the code (just replace downloadDir with your path) and return the file name to the client:

 here is the path: test.mp4 


index.html file
 <head> <title>meteor-methods</title> </head> <body> {{> hello}} </body> <template name="hello"> <form> <input type="text" id="youtube-url" value="https://www.youtube.com/watch?v=alIq_wG9FNk"> <input type="button" id="downloadBtn" value="Download by click"> <input type="submit" value="Download by submit"> </form> </template> 

index.js file:

 'use strict'; if (Meteor.isClient) { //Path = new Meteor.Collection("path"); //Meteor.subscribe("path"); Template.hello.events( { 'submit .form, click #downloadBtn' : function() { var link = document.getElementById("youtube-url").value; //Meteor.call('download', link); Meteor.call('download', link, function(err, path) { if (err) { console.log('Error:', err); } else { console.log("here is the path:", path); } }); event.preventDefault(); } } ); } if (Meteor.isServer) { var fs = Npm.require('fs'); var youtubedl = Npm.require('youtube-dl'); var downloadSync = Meteor.wrapAsync(function(link, callback) { var fname = 'test.mp4'; // by default it will be downloaded to // <project-root>/.meteor/local/build/programs/server/ var downloadDir = './'; console.log('\nStarting download...'); // var dl = youtubedl.download(link, './videos'); var dl = youtubedl(link, [], []); dl.on('info', function(info) { console.log('\nDownload started: ' + info._filename); }); // dl.on('end', function(data) { dl.on('end', function() { console.log('\nDownload finished!'); //callback(null, './videos/' + data.filename); callback(null, fname); }); dl.on('error', function(error) { console.log('\nDownload error:', error); callback(new Meteor.Error(error.message) ); }); dl.pipe(fs.createWriteStream(downloadDir + fname)); }); Meteor.methods({ download: function (link) { return downloadSync(link); } }); } 

Current API does not allow youtube file name to be received when saving a file. If you want to save the file with the youtube file name (as indicated in the initial question), you need to use the getInfo() method of the youtube-dl package.

+3
source

You can use this small package: https://atmosphere.meteor.com/package/client-call . It allows you to call client-side methods from the server in the same way that Meteor.methods be done in another way.

+2
source

I think it would be much easier if you just returned the path you want from the method call. All you have to do is to do youtube download synchronization - it's a kind of meteor way to do something.

This should work:

 if (Meteor.isServer) { var youtubedl = Npm.require('youtube-dl'); var sync = Meteor.wrapAsync(function(url, callback) { var dl = youtubedl.download(link, './videos'); dl.on('end', function(data) { console.log('\nDownload finished!'); callback(null, './videos/' + data.filename); }); }); Meteor.methods({ download: function (link) { return sync(link); } }); } 

Then on the client use:

 Meteor.call('download', link, function(err, path) { console.log("here is the path:", path); }); 
0
source

You should use async Future in the method definition, as described in this. Then you can wait with the client callback only after the async download operation is completed

0
source

Source: https://habr.com/ru/post/950883/


All Articles