- виртмашина с Debian testing на борту, это будет сервер;
- браузер с поддержкой HTML5 File API (Google Chrome 19);
- файл гигов на 5 и более для тестирования.
Виртмашина с дебианчиком
nano .bashrc # для рута и своей учетки shopt -s histappend shopt -s cmdhist export PROMPT_COMMAND='history -a' export HISTFILESIZE=1000 export HISTSIZE=1000 export HISTCONTROL=ignoreboth:erasedups # репозитории nano /etc/apt/sources.list deb http://mirror.yandex.ru/debian/ testing main contrib non-free deb-src http://mirror.yandex.ru/debian/ testing main contrib non-free deb http://security.debian.org/ testing/updates main deb-src http://security.debian.org/ testing/updates main deb http://mirror.yandex.ru/debian/ testing-proposed-updates contrib non-free main deb-src http://mirror.yandex.ru/debian/ testing-proposed-updates contrib non-free main # доустановить пакеты aptitude update; aptitude safe-upgrade; aptitude full-upgrade aptitude install linux-headers-$(uname -r) dkms make gcc g++ aptitude install git-core curl build-essential openssl libssl-dev wget |
Node.js
# mkdir -p /root/build/node && cd $_ # git clone https://github.com/joyent/node.git . # git checkout v0.6.19 # ./configure --openssl-libpath=/usr/lib/ssl # make # make test # make install # node -v |
Socket.io
$ mkdir -p ~/node && cd $_ $ npm install socket.io # после заливки программ на сервер, запустить тут веб-сервер # node app.js |
Код программы
// на клиенте
// по щелчку на кнопке «старт» в сокет уходит событие fileMeta с именем и размером файла;
// на событие «чтение файла ридером» вешается обработчик, который отправляет в сокет событие fileData
//с именем файла и куском считанных бинарных данных
var socket = io.connect(); // connect at the same host / port as your website
...
socket.on('nextChunk', onSocketNextChunk);
socket.on('fileProcessed', onSocketFileProcessed);
...
function onClickStartUpload() {
...
fReader = new FileReader();
...
fReader.onload = function(evnt) {
socket.emit('fileData', {'Name' : fileName, 'Data' : evnt.target.result});
}
socket.emit('fileMeta', {'Name' : fileName, 'Size' : selectedFile.size});
…
// на сервере, приняв событие fileMeta, код проверяет наличие файла и открывает его на запись;
// открытие файла отправляет в сокет (в браузер) событие nextChunk
// с номером следующего чанка и числом отработанных процентов
var app = require('http').createServer(httpResponder)
, io = require('socket.io').listen(app)
...
app.listen(8080); // http://servername:8080/
io.sockets.on('connection', onSocketConnect);
...
function onSocketConnect(socket) {
socket.on( 'fileMeta', function(data) { onSocketFileMeta(data, socket); } );
socket.on( 'fileData', function(data) { onSocketFileData(data, socket); } );
}
…
function onSocketFileMeta (data, socket) {
...
var stat = fs.statSync(fullName);
if(stat.isFile()) {
fObj.rcvdBytes = stat.size;
...
fs.open(fullName, 'a', 0644, function(err, fd) { onFileOpen(err, fd, fName, socket); });
…
function getNextChunk(fObj, socket) {
var pct = (fObj.rcvdBytes / fObj.fSize) * 100;
var chunkNum = fObj.rcvdBytes / chunkSize;
socket.emit('nextChunk', { 'ChunkNum' : chunkNum, 'Percent' : pct });
// на клиенте, обработчик события nextChunk считывает из файла следующий чанк данных,
// после чего обработчик события «чтение файла ридером» отправляет в сокет событие fileData
function onSocketNextChunk (data) {
...
var blob = getNextBlob(selectedFile, nextByte, chunkSize);
fReader.readAsBinaryString(blob);
…
function getNextBlob(fileObj, startPos, numBytes) {
var endPos = startPos + Math.min( numBytes, (fileObj.size - startPos) );
if(fileObj.webkitSlice)
return fileObj.webkitSlice(startPos, endPos);
// на сервере, приняв событие fileData, код ветвится на три дорожки:
// в приемном буфере еще есть место — запросить следующий чанк;
// буфер полон — сбросить его на диск и запросить следующий чанк;
// все данные уже здесь — завершить запись в файл, переместить его в репозиторий и отправить уведомление браузеру
function onSocketFileData(data, socket) {
...
if(fObj.rcvdBytes == fObj.fSize) {
fs.write(fObj.fHandle, fObj.bytesBuf, null, 'Binary', function(err, written, buffer) {
onFileWriteDone(err, written, buffer, fName, socket);
});
}
else if(fObj.bytesBuf.length > bufMaxSize) {
fs.write(fObj.fHandle, fObj.bytesBuf, null, 'Binary', function(err, written, buffer) {
onFileWriteBuffer(err, written, buffer, fName, socket);
});
}
else {
getNextChunk(fObj, socket);
}
…
function onFileWriteBuffer(err, written, buffer, fName, socket) {
fObj.bytesBuf = '';
...
getNextChunk(fObj, socket);
}
…
function onFileWriteDone(err, written, buffer, fName, socket) {
...
fs.close(fObj.fHandle, function(err) {
fObj.fHandle = '';
...
});
...
util.pump(inp, out, function() {
fs.unlink(tempName, function () {
socket.emit('fileProcessed', {'Preview' : 'thumbnail.jpg'});
});
});
}
… и так далее
|
Запуск и тестирование
rsync -av "/home/valik/Desktop/node/" "node:node/"
$ cd node; mkdir Repo; mkdir Temp; node app.js
Полный код аплоадера
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>File Uploader</title>
<script src="/socket.io/socket.io.js"></script>
<script type="text/javascript" charset="utf-8">
window.addEventListener("load", onDocReady);
var socket = io.connect(); // connect at the same host / port as your website
var fReader = ''; // FileReader object
var fileName = ''; // selected file name
var selectedFile = ''; // file object
var mbBytes = 1048576; // bytes in megabyte
var chunkSize = 3 * mbBytes; // chunk size in bytes
socket.on('nextChunk', onSocketNextChunk);
socket.on('fileProcessed', onSocketFileProcessed);
function onDocReady() {
if(window.File && window.FileReader) { //These are the necessary HTML5 objects the we are going to use
document.getElementById('UploadButton').addEventListener('click', onClickStartUpload);
document.getElementById('FileBox').addEventListener('change', onFileChosen);
}
else {
document.getElementById('UploadArea').innerHTML = "Your Browser Doesn't Support The File API. Please Update Your Browser";
}
} // function onDocReady()
function onClickStartUpload() {
if(selectedFile !== '') {
fReader = new FileReader();
fileName = document.getElementById('NameBox').value;
htmlUploading();
fReader.onload = function(evnt) {
socket.emit('fileData', {'Name' : fileName, 'Data' : evnt.target.result});
}
socket.emit('fileMeta', {'Name' : fileName, 'Size' : selectedFile.size});
}
else {
alert("Please Select A File");
}
} // function onClickStartUpload()
function onFileChosen(evnt) {
selectedFile = evnt.target.files[0];
document.getElementById('NameBox').value = selectedFile.name;
} // function onFileChosen(evnt)
function onSocketNextChunk (data) { // socket.emit('nextChunk', { 'ChunkNum' : chunkNum, 'Percent' : pct });
var pct = data['Percent'];
var chunkNum = data['ChunkNum']; // chunk number (0 <= chunkNum < n; n = fSize/chunkSize)
htmlUpdatePct(pct);
var nextByte = chunkNum * chunkSize; //The Next Blocks Starting Position
var blob = getNextBlob(selectedFile, nextByte, chunkSize);
fReader.readAsBinaryString(blob);
} // function onSocketNextChunk (data)
function getNextBlob(fileObj, startPos, numBytes) {
var endPos = startPos + Math.min( numBytes, (fileObj.size - startPos) );
if(fileObj.webkitSlice)
return fileObj.webkitSlice(startPos, endPos);
else
return fileObj.mozSlice(startPos, endPos);
} // function getNextBlob(fileObj, startPos, numBytes)
function htmlUploading() {
var htmlContent = "<span id='NameArea'>Uploading " + selectedFile.name + " as " + fileName + "</span>";
htmlContent += '<div id="ProgressContainer"><div id="ProgressBar"></div></div><span id="percent">0%</span>';
htmlContent += "<span id='Uploaded'> - <span id='MB'>0</span>/" + Math.round(selectedFile.size / mbBytes) + "MB</span>";
document.getElementById('UploadArea').innerHTML = htmlContent;
} // function htmlUploading()
function htmlUpdatePct(percent) {
document.getElementById('ProgressBar').style.width = percent + '%';
document.getElementById('percent').innerHTML = (Math.round(percent * 100) / 100) + '%';
var mbDone = Math.round(((percent / 100.0) * selectedFile.size) / mbBytes);
document.getElementById('MB').innerHTML = mbDone;
} // function htmlUpdatePct(percent)
function onSocketFileProcessed (data) { // socket.emit('fileProcessed', {'Preview' : 'thumbnail.jpg'});
var htmlContent = "File Successfully Uploaded!"
htmlContent += "<img id='Thumb' src='" + data['Preview'] + "' alt='" + fileName + "'><br>";
htmlContent += "<button type='button' name='Upload' value='' id='Restart' class='Button'>Upload Another</button>";
document.getElementById('UploadArea').innerHTML = htmlContent;
document.getElementById('Restart').addEventListener('click', onRefresh);
document.getElementById('UploadBox').style.width = '270px';
document.getElementById('UploadBox').style.height = '270px';
document.getElementById('UploadBox').style.textAlign = 'center';
document.getElementById('Restart').style.left = '20px';
} // function onSocketFileProcessed (data)
function onRefresh() {
location.reload(true);
}
</script>
<style type="text/css" media="screen">
body {
background: #F9F9F9;
font-family: Calibri;
font-size: 18px;
}
h2 {
font-size: 40px;
margin-top: 6px;
margin-bottom: 10px;
}
#Thumb {
max-width: 230px;
max-height: 130px;
}
#ProgressContainer {
width: 396px;
height: 36px;
background: #F8F8F8;
margin-top: 14px;
border: 1px solid #E8E8E8;
border-top: 1px solid #D8D8D8;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
padding: 2px;
}
#ProgressBar {
height: 100%;
width: 0%;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
background: -webkit-gradient( linear, left top, left bottom, from(#a50aad), color-stop(0.50, #6b0d6b), to(#4a074a));
}
#UploadBox {
background: #FFF;
padding: 20px;
position: absolute;
top: 50%;
left: 50%;
margin-left: -200px;
margin-top: -150px;
height: 200px;
width: 400px;
border: 1px solid #DFDFDF;
-webkit-box-shadow: 0px 0px 16px 0px rgba(0,0,0,0.2);
-moz-box-shadow: 0px 0px 16px 0px rgba(0,0,0,0.2);
box-shadow: 0px 0px 16px 0px rgba(0,0,0,0.2);
-webkit-border-radius: 11px;
-moz-border-radius: 11px;
border-radius: 11px;
}
button.Button {
font-size: 18px;
color: #ffffff;
padding: 8px 30px;
background: -webkit-gradient( linear, left top, left bottom, from(#a50aad), color-stop(0.50, #6b0d6b), to(#4a074a));
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
border: 1px solid #5b139e;
-webkit-box-shadow: 0px 1px 3px rgba(000,000,000,0.5), inset 0px 0px 3px rgba(255,255,255,0.4);
-moz-box-shadow: 0px 1px 3px rgba(000,000,000,0.5), inset 0px 0px 3px rgba(255,255,255,0.4);
box-shadow: 0px 1px 3px rgba(000,000,000,0.5), inset 0px 0px 3px rgba(255,255,255,0.4);
text-shadow: 0px -1px 0px rgba(000,000,000,0.1), 0px 1px 0px rgba(145,035,145,1);
position: absolute;
bottom: 20px;
right: 20px;
cursor: pointer;
}
button.Button:hover {
background: -webkit-gradient( linear, left top, left bottom, from(#a50aad), color-stop(0.80, #6b0d6b), to(#a50aad));
color: #D3D3D3;
}
button.Button:active {
background: -webkit-gradient( linear, left top, left bottom, from(#4a074a), color-stop(0.80, #6b0d6b), to(#a50aad));
}
input {
margin-top: 10px;
margin-bottom: 8px;
}
input[type=text] {
border: 1px solid #CDCDCD;
border-top: 1px solid #676767;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
font-size: 18px;
padding: 2px;
width: 300px;
margin-left: 10px;
}
</style>
</head>
<body>
<div id="UploadBox">
<h2>Chunked File Uploader</h2>
<span id='UploadArea'>
<label for="FileBox">Choose A File: </label><input type="file" id="FileBox"><br>
<label for="NameBox">Name: </label><input type="text" id="NameBox"><br>
<button type='button' id='UploadButton' class='Button'>Upload</button>
</span>
</div>
</body>
</html>
|
//# -*- mode: javascript; coding: utf-8 -*-
var tempDir = 'Temp';
var targetDir = 'Repo'; // mkdir Temp; mkdir Repo; node app.js
var mbBytes = 1048576; // bytes in megabyte
var chunkSize = 3 * mbBytes; // chunk size in bytes
var bufMaxSize = 10485760; // 10 MB
var app = require('http').createServer(httpResponder)
, io = require('socket.io').listen(app)
, fs = require('fs')
, exec = require('child_process').exec
, util = require('util')
, filesList = {};
app.listen(8080); // http://servername:8080/
io.sockets.on('connection', onSocketConnect);
function httpResponder(req, res) {
fs.readFile(__dirname + '/index.html',
function (err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}
res.writeHead(200);
res.end(data);
}
);
} // function httpResponder(req, res)
function onSocketConnect(socket) {
socket.on( 'fileMeta', function(data) { onSocketFileMeta(data, socket); } );
socket.on( 'fileData', function(data) { onSocketFileData(data, socket); } );
} // function onSocketConnect(socket)
function onSocketFileMeta (data, socket) { // start recieve file
// socket.emit('fileMeta', {'Name' : fileName, 'Size' : selectedFile.size});
var fName = data['Name'], fSize = data['Size'];
var fullName = tempDir + '/' + fName;
var fObj = { // Create a new Entry in The Files List
fSize : fSize, // file size
bytesBuf : '', // data buffer, 10 MB max
rcvdBytes : 0, // count of recieved bytes
fHandle : '' // file handle
}
try { // if file exist already
var stat = fs.statSync(fullName);
if(stat.isFile()) {
fObj.rcvdBytes = stat.size;
}
}
catch(err) { // It's a New File
console.log(err);
}
filesList[fName] = fObj;
fs.open(fullName, 'a', 0644, function(err, fd) { onFileOpen(err, fd, fName, socket); });
} // function onSocketFileMeta (data, socket)
function onSocketFileData(data, socket) { // socket.emit('fileData', {'Name' : fileName, 'Data' : evnt.target.result});
var fName = data['Name'];
var blob = data['Data']
var fObj = filesList[fName];
fObj.rcvdBytes += blob.length;
fObj.bytesBuf += blob;
filesList[fName] = fObj;
if(fObj.rcvdBytes == fObj.fSize) { //If File is Fully Uploaded
fs.write(fObj.fHandle, fObj.bytesBuf, null, 'Binary', function(err, written, buffer) {
onFileWriteDone(err, written, buffer, fName, socket);
});
} // finish
else if(fObj.bytesBuf.length > bufMaxSize) { //If the Data Buffer reaches 10MB
fs.write(fObj.fHandle, fObj.bytesBuf, null, 'Binary', function(err, written, buffer) {
onFileWriteBuffer(err, written, buffer, fName, socket);
});
}
else {
getNextChunk(fObj, socket);
}
} // function onSocketFileData(data, socket)
function getNextChunk(fObj, socket) {
var pct = (fObj.rcvdBytes / fObj.fSize) * 100;
var chunkNum = fObj.rcvdBytes / chunkSize;
socket.emit('nextChunk', { 'ChunkNum' : chunkNum, 'Percent' : pct });
} // function getNextChunk(fObj, socket)
function onFileOpen(err, fd, fName, socket) {
var fObj = filesList[fName];
if(err) {
console.log(err);
}
else {
fObj.fHandle = fd; // We store the file handler so we can write to it later
filesList[fName] = fObj;
getNextChunk(fObj, socket);
}
} // function onFileOpen(err, fd, fName, socket)
function onFileWriteBuffer(err, written, buffer, fName, socket) { // buffer writed
var fObj = filesList[fName];
fObj.bytesBuf = ''; // Reset The Buffer
filesList[fName] = fObj;
getNextChunk(fObj, socket);
} // function onFileWriteBuffer(err, written, buffer, fName, socket)
function onFileWriteDone(err, written, buffer, fName, socket) {
var fObj = filesList[fName];
fs.close(fObj.fHandle, function(err) {
fObj.fHandle = '';
filesList[fName] = fObj;
});
var tempName = tempDir + '/' + fName;
var inp = fs.createReadStream(tempName);
var out = fs.createWriteStream(targetDir + '/' + fName);
util.pump(inp, out, function() {
fs.unlink(tempName, function () {
socket.emit('fileProcessed', {'Preview' : 'thumbnail.jpg'});
// exec("ffmpeg -i Video/" + Name + " -ss 01:30 -r 1 -an -vframes 1 -f mjpeg Video/" + Name + ".jpg", function(err){
// socket.emit('Done', {'Image' : 'Video/' + Name + '.jpg'});
// });
});
});
} // function onFileWriteDone(err, written, buffer, fName, socket)
|
