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

Reply via email to