Saturday, September 3, 2011

Uploading (not that big) files to Google Sites via Google App Engine with GData Java client

This post will be a quick one.

I lost some time searching through forums and groups on this when I tried to do it and it was actually so simple that I almost got angry not having found it alone...

Some context maybe.
Imagine your Google App Engine application have a form with which you intend to upload a file to Google Sites. The first question is why ? Well... in my case the idea was to use Google Sites as an Android package repository accessible by all my customer's users and actually manage this repository from Google App Engine.

Here is what the Google Sites API documentation says us on uploading attachments.
However using a File on Google App Engine is not that easy.

I could also store a copy of these files in the Blobstore but they're not important enough for me to lose storage quota. This means the ideal way of uploading them to Google Sites would be to "transfer" in the same request than the form POST to our application.

And here is how I did it.

First of all you have get the file that was posted to you application. As a reminder, here is how to do it.
The only difference in our case is that instead of writing the stream back to client, I used  IOUtils.toByteArray method to keep the file in memory, ready to be used for transfer.

String contentFeedUrl = "https://sites.google.com/feeds/content/site/mysitesame";
ContentFeed contentFeed = service.getFeed(
                 new URL(contentFeedUrl + "?kind=filecabinet"),
                 ContentFeed.class);
//I only have one file cabinet page, so I just take the first one.
FileCabinetPageEntry parentPage = contentFeed.getEntries(
                  FileCabinetPageEntry.class).get(0);
String apkType = "application/vnd.android.package-archive";

AttachmentEntry newAttachment = new AttachmentEntry();
//MediaByteArraySource accepts byte array instead of a File
newAttachment.setMediaSource(new MediaByteArraySource(bytes, apkType));
newAttachment.setTitle(new PlainTextConstruct("myAndroidAppName.apk"));
newAttachment.setSummary(new PlainTextConstruct("myAndroidAppDescription"));
newAttachment.addLink(SitesLink.Rel.PARENT,
                                    Link.Type.ATOM,
                                    parentPage.getSelfLink().getHref());

newAttachment = sitesService.insert(new URL(contentFeedUrl), newAttachment);
String link = newAttachment.getLink(Rel.ALTERNATE, apkType).getHref();
//Guess you'll want to keep this link somewhere.
return link;

This that simple. As stated in the title, I didn't test this with big files and I really don't what would happen when keeping a large file in a byte array... So I leave that to you.
Feel free to let us know your results in the comments.

Also, I'm not telling here this is the only nor the best solution. For example, you could first write the file to the Blobstore, and create a task that will upload it to Google Sites and delete from the Blobstore in a second time.

Having a quick look at the Google Data Java client source code we can also find a MediaStreamSource class that could also do the trick. So I guess this piece of code would work as well, and may even be a better solution to upload larger files:

String apkType = "application/vnd.android.package-archive";
String contentFeedUrl= "https://sites.google.com/feeds/content/site/mysitename";
FileItemIterator iterator = new ServletFileUpload().getItemIterator(req);
while (iterator.hasNext()) {
	FileItemStream item = iterator.next();
	InputStream stream = item.openStream();
				
	if (!item.isFormField()) {
		SitesService service = new SitesService("AttachmentUploader-1");
		//Quick & Bad !! Always prefer OAuth when you can use it.
                service.setUserCredentials("myusername@gmail.com", "mypassword");
		service.setConnectTimeout(5000);
		service.setReadTimeout(5000);
					
		ContentFeed contentFeed = service.getFeed(new URL(contentFeedUrl+ "?kind=filecabinet"), ContentFeed.class);
		FileCabinetPageEntry parentPage = contentFeed.getEntries(FileCabinetPageEntry.class).get(0);
					
		AttachmentEntry newAttachment = new AttachmentEntry();
		newAttachment.setMediaSource(new MediaStreamSource(stream, apkType));
		newAttachment.setTitle(new PlainTextConstruct("myAndroidAppName.apk"));
		newAttachment.setSummary(new PlainTextConstruct("myAndroidAppDescription"));
		newAttachment.addLink(SitesLink.Rel.PARENT, Link.Type.ATOM, parentPage.getSelfLink().getHref());

		newAttachment = service.insert(new URL(contentFeedUrl), newAttachment);
		String link = newAttachment.getLink(Rel.ALTERNATE, apkType).getHref();
		logger.info(link);
	} else{
        //Do what you have to do with other fields...
    }
}

No comments:

Post a Comment