Saturday, April 11, 2009

Upload in Gears 0.5

Uploading in Gears is pretty easy since its HttpRequest object supports uploading imagery data. If you want that uploaded file could serve locally (in offline mode), you need to use LocalServer API. Moreover, it is feasible to upload in chunk for large files as well. It involves the use of Blob API as well as good handling on server side. I found two links that is able to achieve this:
http://snippets.dzone.com/posts/show/6175
http://uploadmovietool.appspot.com/. However, I just show a simple implementation both server side script (Rails) and client side script (JavaScript).

Let's start from select a file to upload with the help of Desktop API, openFiles(). You need to pass the callback, and some option attributes like filter and singleFile. After user has selected a file, the callback will be invoked with one parameter that is a File object of that file. Notice that, this file object has two properties: name and blob. Thus, you can use LocalServer API to capture this file by calling captureBlob() and passing blob object, a unique url for accessing this file, and a content type. If you don't provide a content-type, the browser doesn't know how to view this file when it has intercepted by LocalServer. Usually, it will launch a download of this file. Oop! Don't forget to create a localserver object. I assume you understand gears api good enough.

   var desktop = google.gears.factory.create('beta.desktop');
   desktop.openFiles(
      function(files) {
         var file = files[0];
         if(!file) return;

         //capture file for serving locally
         fileName = file.name;
         blobStore.captureBlob(file.blob, fileName, "image/JPEG");
      },
      { filter: ['.jpg'], singleFile: true }
   );


Next, you need to create a HttpRequest object. Then, you do a post to your server side script. You can set some request headers like: 'Content-Disposition', 'Content-Type', and 'Content-Range'. Gears provides an event handler called onprogress. When this event is triggered, you would get ProgressEvent object. This object has 3 properties: total, loaded, and lengthComputable. These 3 properties are useful in updating UI. Last, you need to pass the blob object to send() of HttpRequest object. You can do this by calling getAsBlob() and passing the url that previously called by captureBlob().

   function upload() {
      var xhrUpload = google.gears.factory.create("beta.httprequest");
      xhrUpload.open("POST", '/upload/files');
      xhrUpload.setRequestHeader('Content-Disposition', 'attachment; filename="' + fileName + '"');
      xhrUpload.setRequestHeader('Content-Type', 'image/JPEG');
      xhrUpload.setRequestHeader('Content-Range', 'bytes ' + file.blob.length);

//Update UI
      xhrUpload.upload.onprogress = function(progressEvent) {
         document.getElementById("status").innerHTML = ((progressEvent.loaded/progressEvent.total)*100).toFixed(0) + "%";
      };

      xhrUpload.onreadystatechange = function() {
         switch(xhrUpload.readyState) {
            case 4: //complete
               document.getElementById("status").innerHTML = "done";
               break;
         }
      };
      xhrUpload.send(blobStore.getAsBlob(fileName));
   }


In Rails, it is quite easy to handle actually. You just need to extract file name of request header 'HTTP_CONTENT_DISPOSITION'. Then, you perform an OS check to write a file of the whole request body. It is a method called raw_post of request object.

class UploadController < ApplicationController
   def files
      path = request.env['HTTP_CONTENT_DISPOSITION'][/^attachment\; filename="([^\"]+)"$/, 1]
      if RUBY_PLATFORM.index("win")
         File.open("public/images/#{path}", "wb") { |f| f.write(request.raw_post) }
      else
         File.open("public/images/#{path}", "w") { |f| f.write(request.raw_post) }
      end
   end
end

No comments:

Subscribe in a Reader