如何在流星中从服务器向客户端发送变量?

时间:2022-08-24 14:47:44

I have a page with text input and a button. When I insert link to youtube video into text field and press the button - video downloads into the local folder.

我有一个带文本输入和按钮的页面。当我将youtube视频的链接插入文本字段并按下按钮 - 视频下载到本地文件夹。

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

问题:如何将下载视频的本地副本的链接发送回客户端?

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

更一般的问题:如何从服务器向客户端发送变量(此变量是临时的,不会存储在任何地方)?

The code I have right now:

我现在的代码:

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 ('collection' part is not working)

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!

4 个解决方案

#1


3  

The answer to the question splits into 2 parts: (a) handling async functions inside Meteor's methods and (b) using youtube-dl package.

该问题的答案分为两部分:(a)处理Meteor方法中的异步函数和(b)使用youtube-dl包。

Async functions inside Meteor's methods

There are basically 2+ ways to work with async functions inside Meteor's methods: using future and using wrapAsync. If you look into Meteor's sources, you'll see that wrapAsync itself using future: https://github.com/meteor/meteor/blob/master/packages/meteor/helpers.js#L90. You can also use fibers directly, but it is not recommended.

在Meteor的方法中,基本上有2种方法可以使用异步函数:使用future和使用wrapAsync。如果你看看Meteor的来源,你会看到wrapAsync本身使用future:https://github.com/meteor/meteor/blob/master/packages/meteor/helpers.js#L90。您也可以直接使用纤维,但不建议使用。

Below are generic examples how to use them:

以下是如何使用它们的一般示例:

'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 may also want look to Meteor.bindEnvironment() and future.resolver() for more complex cases.

对于更复杂的情况,您可能还需要查看Meteor.bindEnvironment()和future.resolver()。

Christian Fritz provided correct pattern for wrapAsync usage, however, during 2 years starting from the time the initial question was asked, the API of youtube-dl package has changed.

Christian Fritz为wrapAsync的使用提供了正确的模式,但是,从提出初始问题开始的两年内,youtube-dl包的API已经改变。

Using youtube-dl package

Because of changes in API, if you run his code, the server throws the exception visible in its console:

由于API的更改,如果您运行其代码,服务器会在其控制台中显示异常:

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

And Meteor returns to client undefined value:

并且Meteor返回客户端未定义的值:

here is the path: undefined

The code below is working (just replace downloadDir with your path) and returning filename to the client:

下面的代码正在工作(只需用你的路径替换downloadDir)并将文件名返回给客户端:

here is the path: test.mp4


File index.html

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

File index.js:

'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 to get youtube's filename when saving the file. If you want to save the file with the youtube's filename (as provided in initial question), you need to use getInfo() method of youtube-dl package.

保存文件时,当前API不允许获取youtube的文件名。如果要使用youtube的文件名保存文件(如初始问题中所述),则需要使用youtube-dl包的getInfo()方法。

#2


2  

You can use this small package: https://atmosphere.meteor.com/package/client-call . It allows to call client-side methods from the server in the same way as Meteor.methods do for the other way.

您可以使用这个小包:https://atmosphere.meteor.com/package/client-call。它允许以与Meteor.methods相反的方式从服务器调用客户端方法。

#3


0  

I think it would be much easier if you just returned the path you want from the method call. All you need to do to accomplish that is to make the youtube download synchronous -- which is sort of the meteor way of doing things.

我想如果你只是从方法调用返回你想要的路径会容易得多。你需要做的就是让youtube下载同步 - 这是一种流行的做事方式。

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);
});

#4


0  

You have to use async Future in Method definition, as explained in this answer. Then you will be able to wait with callback to client only after async download operation has finished

您必须在方法定义中使用async Future,如本答案中所述。然后,只有在异步下载操作完成后,您才能等待回调到客户端

#1


3  

The answer to the question splits into 2 parts: (a) handling async functions inside Meteor's methods and (b) using youtube-dl package.

该问题的答案分为两部分:(a)处理Meteor方法中的异步函数和(b)使用youtube-dl包。

Async functions inside Meteor's methods

There are basically 2+ ways to work with async functions inside Meteor's methods: using future and using wrapAsync. If you look into Meteor's sources, you'll see that wrapAsync itself using future: https://github.com/meteor/meteor/blob/master/packages/meteor/helpers.js#L90. You can also use fibers directly, but it is not recommended.

在Meteor的方法中,基本上有2种方法可以使用异步函数:使用future和使用wrapAsync。如果你看看Meteor的来源,你会看到wrapAsync本身使用future:https://github.com/meteor/meteor/blob/master/packages/meteor/helpers.js#L90。您也可以直接使用纤维,但不建议使用。

Below are generic examples how to use them:

以下是如何使用它们的一般示例:

'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 may also want look to Meteor.bindEnvironment() and future.resolver() for more complex cases.

对于更复杂的情况,您可能还需要查看Meteor.bindEnvironment()和future.resolver()。

Christian Fritz provided correct pattern for wrapAsync usage, however, during 2 years starting from the time the initial question was asked, the API of youtube-dl package has changed.

Christian Fritz为wrapAsync的使用提供了正确的模式,但是,从提出初始问题开始的两年内,youtube-dl包的API已经改变。

Using youtube-dl package

Because of changes in API, if you run his code, the server throws the exception visible in its console:

由于API的更改,如果您运行其代码,服务器会在其控制台中显示异常:

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

And Meteor returns to client undefined value:

并且Meteor返回客户端未定义的值:

here is the path: undefined

The code below is working (just replace downloadDir with your path) and returning filename to the client:

下面的代码正在工作(只需用你的路径替换downloadDir)并将文件名返回给客户端:

here is the path: test.mp4


File index.html

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

File index.js:

'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 to get youtube's filename when saving the file. If you want to save the file with the youtube's filename (as provided in initial question), you need to use getInfo() method of youtube-dl package.

保存文件时,当前API不允许获取youtube的文件名。如果要使用youtube的文件名保存文件(如初始问题中所述),则需要使用youtube-dl包的getInfo()方法。

#2


2  

You can use this small package: https://atmosphere.meteor.com/package/client-call . It allows to call client-side methods from the server in the same way as Meteor.methods do for the other way.

您可以使用这个小包:https://atmosphere.meteor.com/package/client-call。它允许以与Meteor.methods相反的方式从服务器调用客户端方法。

#3


0  

I think it would be much easier if you just returned the path you want from the method call. All you need to do to accomplish that is to make the youtube download synchronous -- which is sort of the meteor way of doing things.

我想如果你只是从方法调用返回你想要的路径会容易得多。你需要做的就是让youtube下载同步 - 这是一种流行的做事方式。

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);
});

#4


0  

You have to use async Future in Method definition, as explained in this answer. Then you will be able to wait with callback to client only after async download operation has finished

您必须在方法定义中使用async Future,如本答案中所述。然后,只有在异步下载操作完成后,您才能等待回调到客户端