私もこれに取り組んでおり、機能するコードがいくつかあります。
最初に明確にするために:
これは機能しているように見えますが、サーバー上のそのメソッドにデータを渡すために Meteor がどのテクノロジーを使用しているかはわかりません。また、進行状況情報を取得する方法もわかりません。
Meteor にはサーバーとのWebSockets接続があり、これをトランスポートとして使用します。したがって、Meteor.callまたはMeteor.applyを呼び出すたびに、パラメーター (存在する場合)をEJSONエンコードし、サーバー側の関数を呼び出して、応答を透過的に返します。
ここでの秘訣は、HTML5 の FileReader Apiを使用してファイルをチャンク単位で読み取り (そうしないと大きなファイルはブラウザーをクラッシュさせるので重要です)、チャンクごとに Meteor.call() を実行することです。
あなたのコードは、私にとってうまくいかなかった2つのことをしています:
- ファイルをバイナリ文字列として読み取っています。Base64 を使用する方がはるかに信頼性が高いことがわかりました。あなたのコードはアップロードすると言いますが、ファイルのチェックサムをチェックして、それらが同じファイルであることを確認しましたか?
- ファイルを一気に読み込んでいます。大きなファイル (数百 MB) はパフォーマンスの問題を引き起こし、私の経験ではブラウザがクラッシュすることさえあります。
わかりました、今あなたの質問に。アップロードしたファイルの量を確認する方法は? チャンクを十分に小さくして、chunks_sent / total_chunks をそのメトリックとして使用できるようにすることができます。または、Meteor サーバー側の呼び出しから Session.set( 'progress', current_size / total_size ) を呼び出し、それを要素にバインドして更新することもできます。
これは、この機能をラップするために取り組んできた jQuery プラグインです。これは完全ではありませんが、ファイルをアップロードするので、役に立つかもしれません。現在、ドラッグドロップでのみファイルを取得します。「参照」ボタンはありません。
免責事項:私は Meteor とノードにまったく慣れていないため、「推奨される」方法で実行されないことがありますが、時間をかけて改善し、最終的には 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