Report #2 - Google Tasks API support to libgdata/Evolution - how to make things stick together
As promised, here comes second blog post with more details on how I implementing JSON support. It is still ongoing as I improve code while adding Google Tasks support itself. As I already described, libgdata is is the GLib wrapper/glue library to Google GData protocol family, which is using XML. As they're slowly phasing it out, all newest APIs are using JSON as the communication format. To complicate things a little bit, they are not using unified query parameters (at least not in a documented way), and have changed names of these parameters. They also got rid of several key attributes which presumably were phased out because of lack of real applications for them. But first of all, let's talk how parsing of normal Google API response is done in libgdata.
How to parse JSON data for libgdata
As we know, Google has enabled many services to use external APIs. As library libgdata covers most of those that can be used in desktop environment. For each service library has its Service, Query, Feed and multiple Entry (for more, look at this overview page in libgdata documentation). Service get's all action as it ensures connection and querying. Query have all the information about, well, query itself (and little bit more, see below). And Feed and Entry classes (and their sub classes) are where the work of actual parsing of information and storing it takes place.
Requests themselves are done by __gdata_service_query (which is called by gdata_service_query, which in turn is called from any query function from Service), using libsoup library. My current change when receiving an answer is to look for headers - when receiving application/atom+xml follow current code path, and when receiving application/json, parse content using my implemented support. Parsing of any content happens using virtual functions, which are chained together. For example, Tasklist object received from Google has several members which are common with any other objects, and few which are unique to this object type. First, feed will be parsed (as a concept it doesn't appear in Tasks API, but list acts same way, although it's attribute set is minimized). Then when parsing list of items/entries from this feed, libgdata will first turn to GDataTaskList parse_json virtual function, which will read those members which it acknowledges, and then pass parsing to parent class if it can't find any, which in this case is GDataEntry. Feed classes are similarly chained, although it seems I won't need one for Tasks support. In the end both Feed and Entry passes the torch to their parent class Parsable, which then collects any members not parsed by upstream and call g_debug with message about unknown attribute. In the end when everything is parsed (answer into Feed, objects into Entries), object is returned to query function and after that to library user.
Query or not to query
While Query class itself is rather simple (it it just a collection of query parameters as properties and a set/get function for each), it has important advanced feature - pagination support. For that Query stores 'next' and 'previous' tags from the XML feed. For new protocols with JSON, things got a little bit complicated, as feed returns only 'nextPageToken' member. In overall, similar to other classes, Query class is chained trough get_query_uri with it's parent, which stores general search parameters, and together they build a query which is then passed to __gdata_service query for retrieving actual data. For now 'previous' support for JSON protocols will be broken, but in the future it could be implemented by storing history of previous pages of query. I also had to disable chaining for Tasks support because as I said at the begining, uri parameters are named quite differently. Instead of that, I just get value of property from parent class GDataQuery, and build uri parameter at GDataTasksQuery level.
Making it work together
As I wrote previously, it all comes together when user requires a specific query, for example, requesting all task lists available to authorized user (in this case gdata_tasks_query_all_tasklists in service/gdata-tasks-service.c). Nothing much has changed in this logic, as we pass all parameters to gdata_service_query, recieve output from libsoup and hope that library will figure out which code path to take after detecting content type. This and other query functions are which are used mostly by library user - Evolution in my case.
0 Comments:
Post a Comment
<< Home