uploading a file in chunks using html5
I am trying to upload a file in chunks using html5's File API and then reassembling it on the server side in php. I am uploading a video but when i merge the files in server side the size has increased and it is becoming a invalid file . Please note the below html5 code only works in browser chrome . Tested in Chrome 9 as this uses file API's slice function . Can some one guide me on this ? Thanks
PHP source
<?php
$target_path = "uploads/";
$tmp_name = $_FILES['fileToUpload']['tmp_name'];
$size = $_FILES['fileToUpload']['size'];
$name = $_FILES['fileToUpload']['name'];
$target_file = $target_path . basename($name);
$complete = "complete.mov";
$com = fopen("uploads/".$complete, "ab");
error_log($target_path);
// Open temp file
$out = fopen($target_file, "wb");
if ( $out ) {
// Read binary input stream and append it to temp file
$in = fopen($tmp_name, "rb");
if ( $in ) {
while ( $buff = fread( $in, 1048576 ) ) {
fwrite($out, $buff);
fwrite($com, $buff);
}
}
fclose($in);
fclose($out);
}
fclose($com);
?>
HTML Source
<!DOCTYPE html>
<html>
<head>
<title>Upload Files using XMLHttpRequest</title>
<script type="text/javascript">
window.BlobBuilder = window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder;
function sendRequest() {
var blob = document.getElementById('fileToUpload').files[0];
const BYTES_PER_CHUNK = 1048576; // 1MB chunk sizes.
const SIZE = blob.size;
var start = 0;
var end = BYTES_PER_CHUNK;
while( start < SIZE ) {
var chunk = blob.slice(start, end);
uploadFile(chunk);
start = end;
end = start + BYTES_PER_CHUNK;
}
}
function fileSelected() {
var file = document.getElementById('fileToUpload').files[0];
if (file) {
var fileSize = 0;
if (file.size > 1024 * 1024)
fileSize = (Math.round(file.size * 100 / (1024 * 1024)) / 100).toString() + 'MB';
else
fileSize = (Math.round(file.size * 100 / 1024) / 100).toString() + 'KB';
document.getElementById('fileName').innerHTML = 'Name: ' + file.name;
document.getElementById('fileSize').innerHTML = 'Size: ' + fileSize;
document.getElementById('fileType').innerHTML = 'Type: ' + file.type;
}
}
function uploadFile(blobFile) {
//var file = document.getElementById('fileToUpload').files[0];
var fd = new FormData();
fd.append("fileToUpload", blobFile);
var xhr = new XMLHttpRequest();
xhr.upload.addEventListener("progress", uploadProgress, false);
xhr.addEventListener("load", uploadComplete, false);
xhr.addEventListener("error", uploadFailed, false);
xhr.addEventListener("abort", uploadCanceled, false);
xhr.open("POST", "upload.php");
xhr.onload = function(e) {
alert("loaded!");
};
xhr.send(fd);
//alert("oen over");
}
function uploadProgress(evt) {
if (evt.lengthComputable) {
var percentComplete = Math.round(evt.loaded * 100 / evt.total);
document.getElementById('progressNumber').innerHTML = percentComplete.toString() + '%';
}
else {
document.getElementById('progressNumber').innerHTML = 'unable to compute';
}
}
function uploadComplete(evt) {
/* This event is raised when the server send back a response */
alert(evt.target.responseText);
}
function uploadFailed(evt) {
alert("There was an error attempting to upload the file.");
}
function uploadCanceled(evt) {
xhr.abort();
xhr = null;
//alert("The upload has been canceled by the user or the browser dropped the connection.");
}
</script>
</head>
<body>
<form id="form1" enctype="multipart/form-data" method="post" action="upload.php">
<div class="row">
<label for="fileToUpload">Select a File to Upload</label><br />
<input type="file" name="fileToUpload" id="fileToUpload" onchange="fileSelected();"/>
<input type="button" value="cancel" onClick="uploadCanceled();"/>
</div>
<div id="fileName"></div>
<div id="fileSize"></div>
<div id="fileType"></div>
<div class="row">
<input type="button" onclick="sendRequest();" value="Upload" />
</div>
<div id="progressNumber"></div>
</form>
</body>
</html>
I tried to solve this problem and found the following things, which hopefully will be of use to you:
1) The JS function you are using for slicing the file is deprecated. I am running Chrome v14 and the console didn't recognize it. I had to change it to this before I could do anything:
var chunk = blob.webkitSlice(start, end);
2) I experimented with much smaller files (about 10MB), but had similar problems - my video was always corrupted after upload. When I compared the original and the 'copy' I noticed one thing peculiar: it seemed like the parts of the file were just mixed up - it was all there but in the wrong order.
I suspect one problem your current program suffers from is not taking measures to make sure that the files are assembled in the correct order. I believe what is happening is that your JS is running uploadFile several times, without waiting for the previous uploads to finish, and the server tries to assemble the files in the order it is received, but that is not the same order the files are sent.
I was able to prove this by getting your code to work (somewhat modified, hacked together just as a proof of concept), by assigning each file a sequence number as it was received, and then after all parts were received assembling them in order. After doing that, I was able to play my video file, after having uploaded it.
I think you are going to have to take a similar measure. Receive all the file chunks, and then assemble them, or at least make sure you're taking the necessary measures to assemble them in order. I'm not sure why your files would grow in size (I did observe this phenomenon, early on), so I suspect it is simply some bizarre side effect from otherwise not synchronizing the file chunks.
One difficulty you are immediately going to have is that the Blob object in Javasacript does not support changing or setting the file name, so you cannot on the client-side give the file a unique identifier that way. What I did, as a simple work around was the following:
var i = 1;
while( start < SIZE ) {
var chunk = blob.webkitSlice(start, end);
uploadFile(chunk, i);
i++;
start = end;
end = start + BYTES_PER_CHUNK;
}
function uploadFile(blobFile, part) {
....
xhr.open("POST", "test.php?num=" + part);
....
}
As you can probably guess on the server side, I just then, use that GET variable to assign an identifier, and use that as a the basis for any other processing that needs to be done on the server.
Anyways, this doesn't directly address the issue of the file size growing, so I can only hope this will help you; I'm curious to see what else you find out!
Hello I have checked your php file. I added some sequirity code to it. And changed the filename attribut and deleted the dubbel file creation. Here it is.
<?php
session_start();
if ($_SESSION['newsession'] == false and $_SESSION['TypeUser'] == 'Admin' ){
$target_path = "../uploads/";
$tmp_name = $_FILES['fileToUpload']['tmp_name'];
$size = $_FILES['fileToUpload']['size'];
$name = $_FILES['fileToUpload']['name'];
$name2 = $_GET['filename'];
$target_file = $target_path.$name;
$complete =$target_path.$name2;
$com = fopen($complete, "ab");
error_log($target_path);
// Open temp file
//$out = fopen($target_file, "wb");
//if ( $out ) {
// Read binary input stream and append it to temp file
$in = fopen($tmp_name, "rb");
if ( $in ) {
while ( $buff = fread( $in, 1048576 ) ) {
// fwrite($out, $buff);
fwrite($com, $buff);
}
}
fclose($in);
//}
//fclose($out);
fclose($com);
}else{
echo'you are not logged in.';
}
?>
For the html part I changed the way the multipart files are uploaded. I put theme into a list and one by one I uploaded it. Here is the code.
<script type="text/javascript" >
function uploadchange() {
var input = document.getElementById("file");
var ul = document.getElementById("uploadlist");
while (ul.hasChildNodes()) {
ul.removeChild(ul.firstChild);
}
for (var i = 0; i < input.files.length; i++) {
var li = document.createElement("li");
thefilesize = input.files[i].fileSize||input.files[i].size;
if (thefilesize > 1024 * 1024){
thefilesize = (Math.round(thefilesize * 100 / (1024 * 1024)) / 100).toString() + 'MB';
}else{
thefilesize = (Math.round(thefilesize * 100 / 1024) / 100).toString() + 'KB';
}
li.innerHTML = input.files[i].name + " " + thefilesize ;
ul.appendChild(li);
}
if(!ul.hasChildNodes()) {
var li = document.createElement("li");
li.innerHTML = 'No Files Selected';
ul.appendChild(li);
}
sendRequest();
}
window.BlobBuilder = window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder;
function sendRequest() {
var blob = document.getElementById('file').files[0];
var BYTES_PER_CHUNK = 1048576; // 1MB chunk sizes.
var SIZE = blob.size;
var start = 0;
var end = BYTES_PER_CHUNK;
window.uploadcounter=0;
window.uploadfilearray = [];
document.getElementById('progressNumber').innerHTML = "Upload: 0 % ";
while( start < SIZE ) {
var chunk = blob.slice(start, end);
window.uploadfilearray[window.uploadcounter]=chunk;
window.uploadcounter=window.uploadcounter+1;
start = end;
end = start + BYTES_PER_CHUNK;
}
window.uploadcounter=0;
uploadFile(window.uploadfilearray[window.uploadcounter],document.getElementById('file').files[0].name);
}
function fileSelected() {
var file = document.getElementById('fileToUpload').files[0];
if (file) {
var fileSize = 0;
if (file.size > 1024 * 1024)
fileSize = (Math.round(file.size * 100 / (1024 * 1024)) / 100).toString() + 'MB';
else
fileSize = (Math.round(file.size * 100 / 1024) / 100).toString() + 'KB';
document.getElementById('fileName').innerHTML = 'Name: ' + file.name;
document.getElementById('fileSize').innerHTML = 'Size: ' + fileSize;
document.getElementById('fileType').innerHTML = 'Type: ' + file.type;
}
}
function uploadFile(blobFile,filename) {
var fd = new FormData();
fd.append("fileToUpload", blobFile);
var xhr = new XMLHttpRequest();
xhr.addEventListener("load", uploadComplete, false);
xhr.addEventListener("error", uploadFailed, false);
xhr.addEventListener("abort", uploadCanceled, false);
xhr.open("POST", "./system/upload2.php?filename="+filename);
xhr.onload = function(e) {
window.uploadcounter=window.uploadcounter+1;
if (window.uploadfilearray.length > window.uploadcounter ){
uploadFile(window.uploadfilearray[window.uploadcounter],document.getElementById('file').files[0].name);
var percentloaded2 = parseInt((window.uploadcounter/window.uploadfilearray.length)*100);
document.getElementById('progressNumber').innerHTML = 'Upload: '+percentloaded2+' % ';
}else{
document.getElementById('progressNumber').innerHTML = "File uploaded";
loadXMLDoc('./system/loaddir.php?url='+ window.currentuploaddir);
}
};
xhr.send(fd);
}
function uploadComplete(evt) {
/* This event is raised when the server send back a response */
if (evt.target.responseText != ""){
alert(evt.target.responseText);
}
}
function uploadFailed(evt) {
alert("There was an error attempting to upload the file.");
}
function uploadCanceled(evt) {
xhr.abort();
xhr = null;
//alert("The upload has been canceled by the user or the browser dropped the connection.");
}
</script>
<LINK HREF="./system/link.css" REL="stylesheet" TYPE="text/css">
</head>
<body>
<div id="fileselector">
<div id="containerback">
</div>
<div id="dirlijst">
</div>
<div id="container">
<h1>Upload file</h1>
<br />
<form name="form1" onSubmit="return uploadFile();" method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>" enctype="multipart/form-data">
<div id="progressNumber"></div>
<input type="file" id="file" multiple name="uploads[]" style="visibility:hidden" onChange="uploadchange();">
<a href="#" onClick="document.getElementById('file').click();return false"><img src="system/iconfilemanager/upload.png" alt="upload file"></a>
<div id="uploadlist">
</div>
</form>
</div>
Updated answer
In chrome ==> Slice Function accepts the second parameter as length.
In FF ==> Slice Function accepts the second parameter as end.
code samples
fileorblob.slice(startingPosition, length) //for chrome
fileorblob.slice(startingPosition, end)//for FF
webkitslice and mozslice
is deprecated use native "slice()"
instead.
BlobBuilder
also deprecated use Blob constructor
.
Resources:
http://updates.html5rocks.com/2012/06/Don-t-Build-Blobs-Construct-Them
https://developer.mozilla.org/en-US/docs/Web/API/Blob
Reading files as chunks and uploading