It seems to me forming one request over multiple files is a bit
overdoing it. If one goes wrong, both server and client may easily get
confused about which it was.. Use chunking! Since from a blob you can
use the blob.slice method, this will be easy.
This implementation will send 100k parts of a file, validate it on
server, send CONTINUE - and proceed until a file is complete. Then, if
more files are present in the GearsUpl queue, we venture forth :)
You might need to uncomment 'console.log'
And you need to instantiate a the Gears, try this in a html script
tag:
if (!window.google) {
document.write("You must have <a href='http://
gears.google.com/'>Google Gears</a> installed and enabled!");
}
else if (parseInt(window.google.gears.factory.version.replace(/\./
gi,"")) < 05210) {
document.write("You must have Gears 0.5.21.0 or newer installed.");
else {
var GUPL = new GearsUpload;
GUPL.addFile(/*GearsBlob*/ myfile);
GUPL.addFile(/*GearsBlob*/ myfile);
GUPL.addFile(/*GearsBlob*/ myfile);
// ...
}
Scripts should be work with somewhat like this:
-= JS Parts =-
-------------------
GearsUpl = function() {
this.mayoverride = {};
this.CHUNKSIZE = 100000; // split up in 100k chunks
this.queue=[ ];
this.filefailed = function(server_says_why) {
console.log("Error with" + this.queue[0].name + " : " +
server_says_why);
this.queue.splice(0,1);
}
this.addFile(fileObj) {
this.queue[this.queue.length] = {
"name":fileObj.name,
"file":fileObj,
"bytes":fileObj.blob.length,
"chunks":(fileObj.blob.length > this.CHUNK_BYTES)?Math.ceil
(fileObj.blob.length/this.CHUNKSIZE):1,
"uploaded":0,
"retryChunk":0
};
}
this.processQueue = function() {
if(this.queue.length > 0) {
if(this.queue[0]['uploaded'] == this.queue[0]['chunks']) {
this.queue.splice(0,1);
console.log('File complete ['+this.queue.name+']');
this.processQueue();
} else {
var f = this.queue[0];
var start=0;
var offset=this.CHUNK_BYTES;
if(f['uploaded'] > 0) start = f['uploaded']*this.CHUNK_BYTES;
var end = start+this.CHUNK_BYTES-1;
if(end > f['bytes']) end = f['bytes']-1;
if( (start+offset) > (f['bytes']-1) ) offset = (f['bytes']-
start);
var thischunk = f['file'].blob.slice(start,offset);
this.sendChunk(f['name'], thischunk,start,end,f['bytes']);
}
}
}
this.sendChunk(filename, chunk, start, end, total) {
var request = google.gears.factory.create('beta.httprequest');
request.open('POST', 'upload.php');
request.setRequestHeader('Content-Disposition', 'attachment;
filename="' + filename + '"');
request.setRequestHeader('Content-Type', 'application/octet-
stream');
// use the 'Content-Description' field to send special requests
regarding file
if(typeof this.mayoverwrite[filename] != "undefined")
request.setRequestHeader('Content-Description',
'overwrite=true');
request.setRequestHeader('Content-Range', 'bytes '+start+'-'+end
+'/'+total);
var self = this; // since its asynchrone, self reference is needed
// now handle the feedback server sends on each request
// I use this json format: { status : integer,
statusmessage:'something textual' }
request.onreadystatechange = function() {
if (request.readyState == 4) {
// e.g. { 'status' : 100, 'statusmessage':'X
Bytes received,
Continue plz'}
eval("var status = "+request.responseText);
if(status['status'] == 100) {
if(self.queue[0]['retryChunk'] > 0)
self.queue[0]['retryChunk']
=0;
self.queue[0]['uploaded']+=1;
console.log("Chunk
"+self.queue[0]['uploaded']+" / "+self.queue[0]
['chunks']+" uploaded. ");
}
else if(status['status'] == 101) {
// resume? FIXME: still a TODO since
chunk offsets are calculated
with zero as base
if(confirm("File exists and may be
resumed, click 'yes' to
continue, otherwise upload is discarded")) {
self.resumefileOffset[self.queue[0]['name']] = status['offset'];
return;
}
}
else if(status['status'] == 102) {
if(self.queue[0]['retryChunk'] < 4) {
self.queue[0]['retryChunk']++;
console.log("Chunk
"+(self.queue[0]['uploaded']+1)+" /
"+self.queue[0]['chunks']+" Failed to upload. Retry attempt
#"+self.queue[0]['retryChunk']+".");
}
else {
self.fileFailed("Upload failed
after five retries. Server says:
" + status.statusmessage);
}
}
else if(status['status'] == 550) {
if(confirm("File allready exists
("+self.queue[0]['name']+"),
want to force overwrite?"))
self.mayoverwrite[self.queue[0]['name']] = true;
else
self.fileFailed(status['statusmessage']);
}
else {
self.fileFailed("[" + status['status']
+ "] " + status
['statusmessage']);
}
self.processQueue();
}
};
request.send( chunk );
}
};
-= PHP Parts =-
<?php
class GUP {
function __construct() {
$this->uploadDir = dirname(__FILE__)."/uploads/"; // remember
trailing slash
$this->validExtensions = array
("png","gif","jpg","jpeg","zip","tar","gz","bz2","7z","rar");
}
var $uploadDir;
var $validExtensions;
var $messages = array(
'ERROR_UNKNOWN' => array(
'status' => 500,
'statusmessage' => "There was an unexpected error."
),
'ERROR_PERMISSION' => array(
'status' => 405,
'statusmessage' => "Permissions on server are
inadequate."
),
'ERROR_EXISTS' => array(
'status' => 550,
'statusmessage' => "File exists, request overwrite"
),
'CONTINUE' => array(
'status' => 100,
'statusmessage'=> "Partial upload complete."
),
'COMPLETE' => array(
'status' => 100,
'statusmessage'=> "Upload complete."
),
'RESUME' => array(
'status' => 101,
'offset' => 0,
'statusmessage'=> "Resuming upload, started
earlier in this
browsersession."
),
'INVALID_RANGE' => array(
'status '=> 102,
'statusmessage'=> "Received number of bytes does
not match expected
range."
),
'INVALID_EXTENSION' => array(
'status '=> 103,
'statusmessage'=> "Filename does not match a
valid extension."
)
);
function setExtensions($str) {
$this->validExtensions = explode(",", $str);
for($i = 0 ; $i < count($this->validExtensions); $i++)
$this->validExtensions[$i] =
trim($this->validExtensions[$i]);
}
function checkExtension($filename) {
if(!strstr($filename, ".")) return false;
$ext = substr($filename, strrpos($filename, ".")+1);
foreach($this->validExtensions as $valid)
if($valid == $ext)
return true;
return false;
}
function getByteRange() {
if(!isset($_SERVER['HTTP_CONTENT_RANGE'])) return null;
$range = preg_replace("@.*bytes\ ([.]*)@", "$1", $_SERVER
['HTTP_CONTENT_RANGE']);
$range = explode('/', $range);
$ret = explode('-', $range[0]);
$ret[] = $range[1];
return $ret; // [ start, end, total ]
}
function checkByteRange($receivedLength) {
$range = $this->getByteRange();
return ( $range == null ? false : ($range[1]-$range[0]) ==
$receivedLength);
}
function checkComplete($targetFile) {
$filename = $this->uploadDir.$targetFile;
if(!file_exists($filename))
return false;
else if(!isset($_SERVER['HTTP_CONTENT_RANGE']))
return false;
$total = substr($_SERVER['HTTP_CONTENT_RANGE'], strrpos($_SERVER
['HTTP_CONTENT_RANGE'], '/')+1);
return (filesize($targetFile) == $total);
}
function getFileName() {
return preg_replace('@.*filename="([^"]*).*@', "$1", $_SERVER
["HTTP_CONTENT_DISPOSITION"]);
}
function getMayOverWrite() {
if(!strstr($_SERVER['HTTP_CONTENT_DESCRIPTION'], "overwrite"))
return false;
$ok =
preg_replace('@.*overwrite=(true|false|TRUE|FALSE|0|1).*@',
"$1",
$_SERVER['HTTP_CONTENT_DESCRIPTION']);
eval(" \$overwrite = ".(strlen($ok) == 0 ? "false" : $ok).";");
//
evaluate
return !!$overwrite; // force boolean conversion
}
function getMayResume() {
if(!strstr($_SERVER['HTTP_CONTENT_DESCRIPTION'], "resume"))
return false;
$ok = preg_replace('@.*resume=(true|false|TRUE|FALSE|0|1).*@',
"$1",
$_SERVER['HTTP_CONTENT_DESCRIPTION']);
eval(" \$overwrite = ".(strlen($ok) == 0 ? "false" : $ok).";");
//
evaluate
return !!$overwrite; // force boolean conversion
}
function readFromInputStream($targetFile) {
$fd = @fopen("php://input", "r");
$received = 0;
ob_start();
while( $data = fread( $fd, 10000000 ) ) {
$received = $received + file_put_contents(
$this->uploadDir.
$targetFile, $data, FILE_APPEND );
}
@fclose($fd);
if(strstr(ob_get_clean(), "permission"))
{ echo
json_encode($this->messages['ERROR_PERMISSION']); exit; }
return $received;
}
function handleStream() {
if(strstr($_SERVER["HTTP_CONTENT_DISPOSITION"], "attachment")) {
session_start();
foreach($_SERVER as $key=>$val) if(strstr($key,
"HTTP_")) echo
"[ $key ] => $val\n";
$ok = false;
$name = $this->getFileName();
if(!isset($_SESSION['uploadSession'][$name])) {
// test if file exists
if(file_exists($this->uploadDir.$name))
{
if(!$this->getMayOverwrite()) {
echo
json_encode($this->messages['ERROR_EXISTS']);
} else $ok = true;
} else {
if(!$this->checkExtension($name))
echo
json_encode($this->messages['INVALID_EXTENSION']);
else $ok = true;
}
if($ok) $_SESSION['uploadSession'][$name] =
true;
} else {
// are we resuming?
$range = $this->getByteRange();
if(file_exists($this->uploadDir.$name)
&& filesize($this->uploadDir.$name) !=
$range[0]) {
if(!$this->getMayOverwrite()) {
$this->messages['RESUME']['offset'] = filesize($this->uploadDir.
$name)+1;
echo
json_encode($this->messages['ERROR_EXISTS']);
// FIXME: NOT IMPLEMENTED IN JS
echo json_encode($this->messages
['RESUME']);
}
else if($this->getMayResume()) $ok =
true;
}
}
if($ok) {
$bytes = $this->readFromInputStream();
if($this->checkComplete($name)) {
unset($_SESSION['uploadSession'][$name]);
echo
json_encode($this->messages['COMPLETE']);
} else
echo
json_encode($this->messages['CONTINUE']);
}
}
}
}
$uploadHandle = new GUP();
$uploadHandle->handleStream();
?>
As said, is not entirely tested, but it should give the basic idea..
The most basic php-handling would be somewhat like this:
$fd = @fopen("php://input", "r");
while( $data = fread( $fd, 10000000 ) ) {file_put_contents
( GUP::getFileName(), $data, FILE_APPEND );}
fclose($fd);
echo '{"status":100, "statusmessage":"seed me!"}';
Feel free to ask and subject bugs or errors, anytime!
Best Regards
Morten Sigsgaard
-------------------
On 28 Sep., 15:22, Željko Mitrović <[email protected]> wrote:
> -- snip --
> gearsHttpRequest.open('POST', myUrl);
> gearsHttpRequest.setRequestHeader('content-type',
> 'multipart/form-data;boundary=' + boundary);
>
> gearsHttpRequest.send(builder.getAsBlob());
> -- snip --
Antal <[email protected]>
>
>
>
> > Hello,
>
> > I have searched over a day now, but I still can't find a good solution
> > to post my blobs from my local storage to a remote server. I only
> > found ones that partially worked. Can somebody post me a good example
> > that would help me understand how to do this?
>
> > I am using PHP server side, so the best solution would be something
> > that could send data that would populate the $_FILES and the $_POST
> > (send supplementary data with the file), but of course any other
> > working solution would be good.
>
> > Thank you in advance,
> > István