We have out data seperated in chunks now. Now we have to write a script to send this data to the PHP-script or servlet to save it in a file. This is the elementary part of the the javascript-function and also the simple part. Because we append every chunk to the final file, we can’t work with asynchron requests. If you want to, you have to send also the number of the chunk, and also the final filesize to create a dummy-file and replace the part of the sent chunk. For the javascript the changes are no this big, but the server-side script become more complex.
So.. we will use the synchrone style and work with a simple for-loop to run through our array with the data chunks.
var result=null;
for(var i=0;i<chunks.length;i++){
var last=false;
if(i==(chunks.length-1)){
last=true;
}
result=uploadFileChunk(chunks,filenamePrefix+file.name,url,params,last);
}
return result;
Only the result of the request of the last chunk is returned. Because it is the chunk where the magic (copy from temp-folder to real target folder and create and save entity to database, etc) happens.
I send add a prefix to the filename to create a filename like userid+prefix+filename+fileextension (like *.part or somethin like this). So a user can upload more than one file with the same filename, without cause problems on server side.
But you should also send the original filename as something like "clearname". In this example you can add it to die params-array (params["clearname"]=file.name;) and use it as filename in the db-entity to set this name in header-data on download of this file or in lists of the uploaded files.
function uploadFileChunk(chunk,filename,url,params,lastChunk) {
var formData = new FormData();
formData.append('upfile', chunk, filename);
formData.append("filename",filename);
for(key in params){
formData.append(key,params[key]);
}
Now we have a file-object, we need to split it in small chunks to upload this chunks one by one. JavaScript have the ability to work with files. It can’t access the filesystem directly, but the open- and save-dialogs will do every thing we need. To save data for more than one session you can use the indexeddb. But here we need only open,save and drag and drop.
function createChunksOfFile(file,chunkSize){
var chunks=new Array();
var filesize=file.size;
var counter=0;
while(filesize>counter){
var chunk=file.slice(counter,(counter+chunkSize));
counter=counter+chunkSize;
chunks[chunks.length]=chunk;
}
return chunks;
}
The method is very simple. The loop runs as long as the copied size is smaller as the size of the file. With slice(from,to) you have to set the start- and the endpoint, that is read (in bytes). If the endpoint behind the real end of the file no execption is thrown and only the existing part will be copied. That makes it very easy to us. With every run of the loop we add 250000 to the copied-size till our copied-size is greater than the file-size. In ervery run of the loop we copy/slice the bytes from copied-size to copied-size + 250000 and add this slice-chunk to the output array.
Finally we get an array with alle the sliced chunks of the file in correct order.
Yes.. you can calculate the needed count of chunks and than use a for-loop and calculate the start and end for every part. Works great too… but i did it the other way.
var chunks=createChunksOfFile(file,250000);
So we have all chunks each ~250KB. 1MB should have ~4 Parts. Yes.. it’s 1024Bytes per 1KB.. but we keep it simple :-)
A simple file-upload is easy to implement. A input-tag of the "file"-type. Set "method" to "post" and let the action attribute point to the target-page. Don’t forget enctype="multipart/form-data" and every thing is finished.
This implementation is very limited and in modern Web-Apps you want to do more than simply upload files. You want to scale, rotate and process the image. You want to see the progress of the upload and upload more than one file at once in the upload-queue. A few years earlier you had to use flash.. but flash is dead. Today we use HTML5 + JavaScript. With ist you can do what ever you want to do. The File-API helps you to load local files per input or drag and drop. A web-notification informs you when the upload is finished… very helpful if it is a very large file.
This file-upload that is created in this posts is not perfect, but it works in many of my projects and scripts. The target-script can be implemented very easy in PHP or as a servlet.
For a simple test you can use this PHP-script:
PHP:
<?php
//see $_REQUEST["lastChunk"] to know if it is the last chunk-part or not
if(isset($_FILES["upfile"])){
file_put_contents($_REQUEST["filename"],file_get_contents($_FILES["upfile"]["tmp_name"]),FILE_APPEND);
}
?>
for Java you can use this (FileIOToolKit is a class of my own, it simply adds a byte[] to the end of a file or creates the file, if it doesn’t exist):
String filename = request.getParameter("filename");
for (Part part : request.getParts()) {
if (part.getName().equals("upfile")) {
byte[] out = new byte[part.getInputStream().available()];
part.getInputStream().read(out);
As you see, the files aren’t uploaded at once, but in multiple parts. So ist is very easy to track the progress of the upload.
To load a file in HTML5 is very easy. Drag and drop or the OnChange-Action of a file-input element delivers you a event, that contains a list of files.
Here is simple example to get the files from the event. The two events from the input and the one from the drag and drop are different.
var files = null; // FileList object
if(!nodragndrop){
files=evt.dataTransfer.files; //input
}
else{
files=evt.target.files; //drag and drop
}
But we don’t care about this part of loading and starts where we have the file-object. You also can create a binary-blob from an data-URL (from a canvas for example).
Nun haben wir unsere Daten in entsprechend aufbereiteter Form. Was noch fehlt ist, dass wir die Daten an unser Script senden, das dann alles Speichert. Das ist an sich der elementare Teil hier und wie wohl erwartet an sich auch der einfachste Teil. Da wir einfach mit append immer hinten an die Datei ran schreiben, bleiben wir synchron. Sollte man asynchron werden wollen, müßte man die Nummer des aktuellen Teils und die gesamt Zahl der Teile mitsenden. Dann och die Größe der Teile und man könnte eine Dummy-Datei erzeugen mit der gesamten Größe und dann die Teile immer an den entsprechenden Bereichen einfügen. Sollte an sich auch nicht so kompliziert sein und am JavaScript-Code würde sich kaum was ändern, ausser den paar mehr Infos im Request.
Aber erstmal alles Synchron, weil wir dann auch einfach mit einer for-Schleife durch unser Array durch laufen können.
var result;
for(var i=0;i<chunks.length;i++){
var last=false;
if(i==(chunks.length-1)){
last=true;
}
result=uploadFileChunk(chunks,filenamePrefix+file.name,url,params,last);
}
return result;
Es wird nur das Result des letzten Teil-Uploads zurück geliefert. Weil dort dann meistens auch die Datei nochmal umkopiert wird und entsprechende Datenbankeinträge vorgenommen werden. Es ist gut die Datei erstmal in einem separaten Verzeichnis als *.part oder so zu speichern und erst wenn der letzte Teil (last Variable) angekommen und gespeichert die DB-Einträge und an einen entsprechenden Ort zu kopieren. Über den Ordner mit den *.part Dateien kann dann ein Cron-Job laufen der alle dort vorhanden *.part Dateien entfernt die länger als 20min nicht geändert wurden.
Ich übergebe noch einen Prefix für den Filename, dann kann am Server userid+filenname+prefix+parterweiterung als Dateiname verwendet werden. Damit ist es auch für einen User möglich Dateien mit selben Dateinamen hochzuladen ohne dass es am Server zu Verwechselungen kommt.
Idealer Weise sollte noch mal der eigentliche Dateiname zusätzlich noch mal mit übergeben werden. Ist hier im Beispiel nicht so hat direkt ersichtlich, weil der Name mit in den "params" steht wo auch noch alle möglichen anderen Daten für das Request mit drin stehen können.
function uploadFileChunk(chunk,filename,url,params,lastChunk) {
var formData = new FormData();
formData.append('upfile', chunk, filename);
formData.append("filename",filename);
for(key in params){
formData.append(key,params[key]);
}
Hier der eigentliche Uplaod-Code. Ansich entspricht es einer Form nur eben rein in JavaScript. Die Daten der Form werden dann per AJAX-Request an das Script geschickt.
Nach dem wir nun unser File-Object haben, wollen wir es in kleine Teile zerlegen, die wir dann einzelnd hoch laden
können. JavaScript kann seit einiger Zeit super mit Dateien umgehen. Direkter Zugriff auf das Dateisystem ist natürlich nicht möglich aber Öffnen- und Speicherdialoge reichen ja auch. Um anders Datenabzulegen gibt es noch die indexeddb von JavaScript auf dich in vielleicht in einem weitern Eintrag mal eingehe. Aber ansosnten kommen wir mit Öffnen/Drag and Drop und Speichern vollkommen aus.
function createChunksOfFile(file,chunkSize){
var chunks=new Array();
var filesize=file.size;
var counter=0;
while(filesize>counter){
var chunk=file.slice(counter,(counter+chunkSize));
counter=counter+chunkSize;
chunks[chunks.length]=chunk;
}
return chunks;
}
Die Methode arbeitet an sich ganz einfach. Die Schleife läuft so lange wie die kopierte Größe kleiner ist als die Gesamtgröße der Datei. Bei slice(from,to) gibt man den Anfang und das Ende an. Wenn das Ende hinter dem realen Ende der Datei liegt wird nur das was noch vorhanden war kopiert und kein Fehler geworfen, was es uns hier sehr einfach macht. Wir addieren also z.B. bei jeden Durchlauf 250000 auf die aktuelle Kopie-Größe rauf, bis wir über der Dateigröße liegen. Bei jedem dieser Durchläufe wird von der Position der Größe der Kopie bis zu der Größe + Größe des zu kopierenden Teils, der Teil der Datei mit in ein Array kopiert.
Am Ende haben wir also ein Array mit den Teilen der Datei in der korrekten Reihenfolge.
Man hätte natürlich vorher die Anzahl der Teile ausrechenen können und dann mit einer for-Schleife und für jeden Teil die Position in der Datei berechnen können.. ich fand es so aber erstmal einfacher.
var chunks=createChunksOfFile(file,250000);
Damit erhalten wir Array mit alleien Teilen der Datei zu je ~250KB. Eine Datei von 1MB hätte also ~4 Teile. Alles ungefähr weil eben 250000 keine exakten 250KB sind (1024Byte wären ja 1KB... aber das will uns hier mal nicht interessieren).
Blog-entries by search-pattern/Tags:
Möchtest Du AdSense-Werbung erlauben und mir damit helfen die laufenden Kosten des Blogs tragen zu können?