通过Meteor获取上传进度

时间:2013-02-11 04:11:28

标签: meteor

当我意识到由于缺少路由而没有终点时,我正准备使用XHR将文件发送到服务器。

然后我阅读this article并发现我可以利用Meteor.methods进行文件上传。现在我的上传看起来像这样:

$(function() {
    $(document.body).html(Meteor.render(Template.files));
    $(document).on('drop', function(dropEvent) {
        _.each(dropEvent.originalEvent.dataTransfer.files, function(file) {
            var reader = new FileReader();
            reader.onload = function(fileLoadEvent) {
                Meteor.call('uploadFile', file, reader.result);
            };
            reader.readAsBinaryString(file);
        });
        dropEvent.preventDefault();
    });

    $(document).bind('dragover dragenter', function(e) {
        e.preventDefault();
    });

});

server/main.js我有这个:

var require = __meteor_bootstrap__.require; // should I even be doing this? looks like an internal method
var fs = require('fs');
var path = require('path');

Meteor.methods({
    uploadFile: function(fileInfo, fileData) {
        var fn = path.join('.uploads',fileInfo.name);
        fs.writeFile(fn, fileData, 'binary', function(err) {
            if(err) {
                throw new Meteor.Error(500, 'Failed to save file.', err);
            } else {
                console.log('File saved to '+fn);
            }
        });
    }
});

只是将其写入磁盘。这似乎有效,但我不知道Meteor使用什么技术将数据传递给服务器上的该方法,我不知道如何获取进度信息。

通常我会将事件监听器附加到xhr对象

xhr.upload.addEventListener("progress", uploadProgress, false);

但我不认为我可以访问.methods。还有另一种方法吗?

1 个答案:

答案 0 :(得分:6)

我也一直在研究这个问题,我有一些有用的代码。

首先澄清:

  

这似乎有效,但我不知道Meteor使用什么技术将数据传递到服务器上的该方法,我不知道如何获取进度信息。

Meteor与服务器有WebSockets连接,并将其用作传输。因此,每当您致电Meteor.callMeteor.apply时,它EJSON将对您的参数进行编码(如果有),请调用函数服务器端,并以透明方式返回响应。

这里的技巧是然后使用HTML5's FileReader Api以块的形式读取文件(这很重要,因为大文件会使浏览器崩溃),并为每个块执行Meteor.call()。

你的代码做了两件对我来说效果不佳的事情:

  1. 它正在以二进制字符串形式读取文件:我发现使用Base64更加可靠。你说你的代码上传了,但是你检查过文件校验和以确保它们是同一个文件吗?
  2. 它正在同时读取文件。大文件(100 MB)会导致性能问题,而且根据我的经验甚至会导致浏览器崩溃。
  3. 好的,现在问你的问题。如何判断你上传了多少文件?您可以使您的块足够小,以便您可以使用chunks_sent / total_chunks作为指标。 或者您可以从Meteor服务器端调用中调用Session.set('progress',current_size / total_size)并将其绑定到元素以便更新。

    这是一个jQuery插件,我一直在努力包装这个功能。它不完整,但它上传文件,它可能对您有所帮助。它目前只通过拖放获取文件..没有“浏览”按钮。

    免责声明:我对Meteor& amp;节点,所以有些事情可能不会以“推荐”的方式完成,但我会随着时间的推移改进它并最终在Github中给它一个家。

    ;(function($) {
    
        $.uploadWidget = function(el, options) {
    
            var defaults = {
                propertyName: 'value',
                maximumFileSize: 1073741824, //1GB
    
                messageTarget: null
            };
    
            var plugin = this;
    
            plugin.settings = {}
    
    
            var init = function() {
                plugin.settings = $.extend({}, defaults, options);
                plugin.el = el;
    
                if( !$(el).attr('id') || !$(el).attr('id').length ){
                    $(el).attr('id', 'uploadWidget_' + Math.round(Math.random()*1000000));
                }
    
                if( plugin.settings.messageTarget == null ){
                    plugin.settings.messageTarget = plugin.el;
                }
    
                initializeDropArea();
            };
    
    
            // Returns a human-friendly string representation of bytes
            var getBytesAsPrettyString = function( bytes ){
    
                var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
                if (bytes == 0) return 'n/a';
                var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
                return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i];
    
            };
    
    
            // Throws an exception if the file (from a drop event) is unacceptable
            var assertFileIsAcceptable = function( file ){
    
                if( file.size > plugin.settings.maximumFileSize ){
                    throw 'Files can\'t be larger than ' + getBytesAsPrettyString(plugin.settings.maximumFileSize);
                }
    
                //if( !file.name.match(/^.+?\.pdf$/i) ){
                //    throw 'Only pdf files can be uploaded.';
                // }
    
            };
    
    
            var setMainMessage = function( message ){
    
                $(plugin.settings.messageTarget).text( message );
    
            };
    
    
            plugin.getUploader = function(){
    
                return plugin.uploader;
    
            };
    
    
            var initializeDropArea = function(){
    
                var $el = $(plugin.el);
    
                $.event.props.push("dataTransfer");
    
                $el.bind( 'dragenter dragover dragexit', function(){
                    event.stopPropagation();
                    event.preventDefault();
                });
    
                $el.bind( 'drop', function( event ){
    
                    var slices;
                    var total_slices;
    
                    var processChunkUpload = function( blob, index, start, end ){
    
                        var chunk;
    
                        if (blob.webkitSlice) {
                            chunk = blob.webkitSlice(start, end);
                        } else if (blob.mozSlice) {
                            chunk = blob.mozSlice(start, end);
                        } else {
                            chunk = blob.slice(start,end);
                        }
    
                        var reader = new FileReader();
    
                        reader.onload = function(event){
    
                            var base64_chunk = event.target.result.split(',')[1];
    
                            slices--;
    
                            $el.text( slices + ' out of ' + total_slices + ' left' );
    
                            Meteor.apply(
                                'saveUploadFileChunk',
                                [file_name, base64_chunk, slices+1],
                                { wait: true }
                            );
                        };
    
                        reader.readAsDataURL(chunk);
                    };
    
    
                    event.stopPropagation();
                    event.preventDefault();
                    event.dataTransfer.dropEffect = 'copy';
    
                    if( !event.dataTransfer.files.length ){
                        return;
                    }
    
                    const BYTES_PER_CHUNK = 1024 * 1024; // 1MB chunk sizes.
    
    
                    var blob = event.dataTransfer.files[0];
                    var file_name = blob.name;
    
                    var start = 0;
                    var end;
                    var index = 0;
    
                    // calculate the number of slices we will need
                    slices = Math.ceil(blob.size / BYTES_PER_CHUNK);
                    total_slices = slices;
    
                    while(start < blob.size) {
                        end = start + BYTES_PER_CHUNK;
                        if(end > blob.size) {
                            end = blob.size;
                        }
    
                        processChunkUpload( blob, index, start, end );
    
                        start = end;
                        index++;
                    }
    
    
                });
    
            };
    
            init();
    
        }
    
    })(jQuery);
    

    这是我的流星发布方法。

    Meteor.methods({
    
            // this is TOTALLY insecure. For demo purposes only.
            // please note that it will append to an existing file if you upload a file by the same name..
            saveUploadFileChunk: function ( file_name, chunk, chunk_num ) {
    
                    var require = __meteor_bootstrap__.require;
                    var fs = require('fs');
                    var crypto = require('crypto')
    
                    var shasum = crypto.createHash('sha256');
                    shasum.update( file_name );
    
                    var write_file_name = shasum.digest('hex');
    
                    var target_file = '../tmp/' + write_file_name;
    
                    fs.appendFile(
                            target_file,
                            new Buffer(chunk, 'base64'),
                            {
                                    encoding: 'base64',
                                    mode: 438,
                                    flag: 'a'
                            }
                            ,function( err ){
    
                                    if( err ){
    
                                            console.log('error ' + err);
                                    }
    
                                    console.log( 'wrote ' + chunk_num );
    
                            }
                    );
    
                    return write_file_name;
    
            }
    });
    

    HTH