Yeah chunking is good but I tried to simplify things to be easier to understand. When you understand the basics than you have foundation you can build on ;) --- Željko http://skitanja.blogspot.com/
"you can't stay young forever... but you can be immature for the rest of your life" Unknown 2009/10/1 dk_mSigsgaard <[email protected]> > > 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 >
