Tom Clarkson

SharePoint, Startups and some other stuff

Archive for the ‘GData’ Category

Google API – A Moving Target

leave a comment »

A while ago I said that developing for Google Apps was like SharePoint in the lack of documentation and hacks required to work around the issues. I have since discovered something more unique to Google – sudden changes to the behaviour of API calls with neither the old or new behaviour being documented. Quick changes can be good (like the fix for the 404 errors on working with folders I’m hoping will appear in the next week or so – the best workaround I have for that so far is changing the error message to reflect that the bug is in the api rather than my code), but sometimes they just stop my code from working altogether.

The latest issue I’ve run into is with creating documents. Because Google Docs does not support creating new documents offline, GDNote creates a folder full of blank documents that can be moved and renamed while offline. Yesterday this worked, but today the call gets the error 400 Bad Request – Document content required.

We have very good error logging set up, so I should be able to fix it quickly and not too many users will be affected, but these unexpected issues are still pretty frustrating.

Written by Tom Clarkson

March 25, 2009 at 12:42 am

Posted in GData, Startup

Dealing with Google API Bugs

leave a comment »

I just deployed an updated version of GDNote. The bugs fixed are getting fairly small now – the latest one was triggered only if you reload a section while the web service call to create it is running. However, some users are running into issues we can’t fix – bugs in the Google APIs.

The most recent case involves a user getting errors doing anything that creates a folder. Everything works fine with my own heavy usage and all of the test accounts, but for one particular user it doesn’t work. Fortunately GDNote logs all errors in as much detail as possible without causing security issues, so I can see the request that failed. It’s exactly the same api call that works for everyone else, but for this particular user Google returns 404 not found.

A bit of searching reveals that this is a known Google bug that has shown up in the last week or so, with a fix due later this month. Best I can do is wait for the fix and tell the affected user to create the folders manually. For similar issues in the past I’ve been able to create workarounds in the code, for example checking if a call really failed or just returned an error code for no apparent reason. What I can’t do is find a way to prevent these problems from occurring for real users in the future. Normally when an issue comes up and you fix it you would add a test that reproduces the original error, preferably before a real user has problems. But with these api problems the issue only occurs for some accounts, and setting up a test account with the same configuration doesn’t trigger the error.

Is it actually possible to do anything better than “Works on My Machine” when working with cloud APIs?

Written by Tom Clarkson

March 18, 2009 at 7:58 pm

Posted in GData, Startup

Uploading HTML with Images to Google Docs

with one comment

Here’s another Google Docs API trick. Using the standard .NET API only word documents can be uploaded with embedded images. Embedding images in the HTML doesn’t work, and any images linked in the html will not be copied to the Google server.

However, if you upload an image file to Google docs, it will be converted into a document – the image gets a file id and a document is created containing a link to that image. This feature can be used to upload any necessary images before sending the html with the full content.

  1. Upload an image, naming it “Temporary Image Upload Doc” or something similarly obviously temporary
  2. Download the resulting document and parse the content to find the img tag with src starting with File?id=
  3. Delete the temporary document
  4. Update the main html content to use the file url obtained from the temporary document
  5. Upload the complete html document.

Image upload code is below. Note that I had to create the request manually rather than using service.UploadDocument – even though the server will accept images, the .NET api does not list them as valid content types.

        ///
        /// Upload an image for embedding in a document
        ///
        ///
        ///
        /// The id of the uploaded image
        public static string UploadImage(string notesFolderId, Stream imageStream, string contentType, string token)
        {

            var service = GetService(token);

            // upload
            // standard .net api won't accept png

            var respstr = service.StreamSend(new Uri("http://docs.google.com/feeds/documents/private/full"), imageStream, GDataRequestType.Insert, "image/png", "Temporary Image Upload File");
            var rssr = new StreamReader(respstr);

            var se = rssr.ReadToEnd();

            // get ids from response

            var tempDocId = GetDocIdFromResponseString(se);

            // Download document

            // TODO: this can be made a bit more efficient if we get the media uri from
            // the original response
            var e = service.Get(string.Format("http://docs.google.com/feeds/documents/private/full/document%3A{0}", tempDocId));

            var resp = DownloadDocumentFromAltUri(e.AlternateUri.Content, token);

            // delete temp doc

            var tempDocEditUri = e.EditUri.Content;
            var tempDocMediaUri =
                string.Format("http://docs.google.com/feeds/media/private/full/document%3A{0}/{1}",
                tempDocId,
                tempDocEditUri.Substring(tempDocEditUri.LastIndexOf("/") + 1));

            service.Delete(new Uri(tempDocMediaUri));

            //
            var mc = Regex.Matches(resp, "File\\?id=(.*?)'");

            if (mc.Count < 1 || mc[0].Groups.Count < 2) {
                throw new Exception("Unable to get image id from response");
            }
            return mc[0].Groups[1].Value;

        }

Written by Tom Clarkson

January 28, 2009 at 8:23 am

Posted in GData

Downloading Documents with the Google Docs API

leave a comment »

Lately I’ve been working on an app that uses the Google Docs GData API. I’ve found that Google Apps shares a lot of the properties that make SharePoint development particularly interesting:

  • Searching for error messages doesn’t help much because the platform and its bugs are quite new
  • The tools have pieces missing and don’t quite work as they should
  • Documentation is inadequate at best

One of the most obviously missing functions in the API is the ability to download a document. I found a workaround on google groups easily enough, creating the http request manually and setting the Authorization header, but unfortunately that doesn’t seem to work for hosted accounts.

With a few wrong turns trying to get a new auth token manually, it took me a full day of trial and error to find something that worked – setting a cookie and adding the hosted domain name to the start of the auth token.

To save anyone else from going through the same process, here’s the code:

  

        public static string DownloadDocumentFromAltUri(string altUri, string token)
        {
            var service = GetService(token);
            string docurl;
            string domain = null;

            var docId = altUri.ToDocumentId();

            if (altUri.Contains("docs.google.com/a/"))
            {
                // google apps

                var s1 = altUri.Substring(altUri.IndexOf("docs.google.com/a/") + 18);
                domain = s1.Substring(0, s1.IndexOf("/"));

                docurl = string.Format("http://docs.google.com/a/{0}/RawDocContents?revision=_latest&docID={1}&justBody=true&browserok=true", domain, docId);
            }
            else
            {
                // google docs
                docurl = string.Format("http://docs.google.com/RawDocContents?revision=_latest&docID={0}&justBody=true&browserok=true", docId);
            }

            Debug.WriteLine(string.Format("Downloading. DocId={0}, AltUri={1}, DocUri={2}", docId, altUri, docurl));

            HttpWebRequest wreq = (HttpWebRequest)HttpWebRequest.Create(docurl);
            if (UseAuthSub)
            {
                wreq.Headers.Add("Authorization", string.Format("AuthSub token={0}", token));
            }
            else
            {
                var authToken = ((GDataGAuthRequestFactory)service.RequestFactory).QueryAuthToken(service.Credentials);

                if (string.IsNullOrEmpty(domain))
                {
                    // unhosted accounts should accept this header
                    wreq.Headers.Add("Authorization", string.Format("GoogleLogin auth={0}", authToken));
                }
                else
                {
                    wreq.Headers.Add("Cookie",
                           string.Format("WRITELYH={0}={1}",
                                    domain,
                                    authToken
                                    )

                        );

                }

            }

            wreq.KeepAlive = true;
            var wresp = wreq.GetResponse();
            var stream1 = wresp.GetResponseStream();
            var cl = wresp.ContentLength;
            StreamReader sr = new StreamReader(stream1);
            var resp = sr.ReadToEnd();
            return resp;
        }

Written by Tom Clarkson

January 28, 2009 at 8:17 am

Posted in GData

Follow

Get every new post delivered to your Inbox.