Updated contact's get_changes with the working changes for calendar's function
[opensync/google-calendar-cdf.git] / src / gcalendar.c
blobda2e8cc3923d4dc27babfa479a9b30267091167f
1 /** Google Calendar plugin
3 * Copyright (c) 2006 Eduardo Pereira Habkost <ehabkost@raisama.net>
4 * Copyright (c) 2008 Adenilson Cavalcanti da Silva <adenilson.silva@indt.org.br>
5 * Copyright (c) 2010 Chris Frey <cdfrey@foursquare.net> for NetDirect Inc.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Lesser Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20 * 02110-1301 USA
24 /* TODO:
25 * - review code for leaks
29 #include <opensync/opensync.h>
30 #include <opensync/opensync-plugin.h>
31 #include <opensync/opensync-helper.h>
32 #include <opensync/opensync-capabilities.h>
33 #include <opensync/opensync-format.h>
34 #include <opensync/opensync-xmlformat.h>
35 #include <opensync/opensync-data.h>
36 #include <opensync/opensync-version.h>
38 #include <glib.h>
40 #include <libxml/tree.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <ctype.h>
46 #include <sys/types.h>
47 #include <signal.h>
48 #include <sys/wait.h>
49 #include <gcal_status.h>
50 #include <gcalendar.h>
51 #include <gcontact.h>
52 #include "xslt_aux.h"
54 #include "timestamp.h"
55 #include <time.h>
56 #include <sys/time.h>
58 static void gc_connect(OSyncObjTypeSink *sink, OSyncPluginInfo *info,
59 OSyncContext *ctx, void *data);
60 static void gc_get_changes_calendar(OSyncObjTypeSink *sink,
61 OSyncPluginInfo *info, OSyncContext *ctx,
62 osync_bool slow_sync, void *data);
63 static void gc_get_changes_contact(OSyncObjTypeSink *sink,
64 OSyncPluginInfo *info, OSyncContext *ctx,
65 osync_bool slow_sync, void *data);
66 static void gc_commit_change_calendar(OSyncObjTypeSink *sink,
67 OSyncPluginInfo *info, OSyncContext *ctx,
68 OSyncChange *change, void *data);
69 static void gc_commit_change_contact(OSyncObjTypeSink *sink,
70 OSyncPluginInfo *info, OSyncContext *ctx,
71 OSyncChange *change, void *data);
72 static void gc_sync_done(OSyncObjTypeSink *sink, OSyncPluginInfo *info,
73 OSyncContext *ctx, void *data);
75 static int timestamp_cmp(const char *timestamp1, const char *timestamp2)
77 // timestamp (RFC3339) formating string
78 char format[] = "%FT%T";
79 struct tm first, second;
80 time_t t_first, t_second;
81 int result = 0;
83 if (!timestamp1 && !timestamp2)
84 return 0;
85 if (!timestamp2)
86 return 1;
87 if (!timestamp1)
88 return -1;
90 // From timestamp string to time structure
91 timestamp2tm(timestamp1, format, &first);
92 timestamp2tm(timestamp2, format, &second);
95 // From time structure to calendar time (since
96 // Epoch (00:00:00 UTC, January 1, 1970)
98 t_first = mktime(&first);
99 t_second = mktime(&second);
101 if (t_first == t_second)
102 result = 0;
103 else if (t_first > t_second)
104 result = 1;
105 else if (t_first < t_second)
106 result = -1;
108 return result;
112 const char* findstr(const char *data, const char *format)
114 const char *match = format;
115 const char *start = data;
117 while( *data && *match ) {
118 int matched = 0;
120 switch( *match )
122 case '9':
123 matched = isdigit(*data);
124 break;
125 default:
126 matched = (*data == *match);
127 break;
130 if( matched ) {
131 data++;
132 match++;
133 if( *match == 0 )
134 return start;
136 else {
137 data++;
138 start = data;
139 match = format;
143 return data;
146 // vtime2gtime
148 // Searches through data, converting every vtime string it finds into
149 // a timestamp compatible with Google. i.e. it converts time in the
150 // format: YYYYMMDDTHHMMSSZ to YYYY-MM-DDTHH:MM:SSZ
152 // Caller is responsible for freeing the returned string.
154 char* vtime2gtime(const char *data)
156 char *ret = malloc(strlen(data) * 2);
157 char *target = ret;
159 while( *data ) {
160 // search for YYYYMMDDTHHMMSS
161 const char *match = findstr(data, "99999999T999999");
163 // copy the non-matching data
164 memcpy(target, data, match - data);
165 target += match - data;
167 // was there a match?
168 if( *match ) {
169 // adjust the timestamp
170 target[0] = match[0];
171 target[1] = match[1];
172 target[2] = match[2];
173 target[3] = match[3];
174 target[4] = '-';
175 target[5] = match[4];
176 target[6] = match[5];
177 target[7] = '-';
178 target[8] = match[6];
179 target[9] = match[7];
180 target[10] = match[8];
181 target[11] = match[9];
182 target[12] = match[10];
183 target[13] = ':';
184 target[14] = match[11];
185 target[15] = match[12];
186 target[16] = ':';
187 target[17] = match[13];
188 target[18] = match[14];
189 target += 19;
190 data = match + 15;
192 else {
193 data = match;
196 *target = 0;
197 return ret;
200 struct gc_plgdata;
202 struct gc_gdata
204 // parent
205 struct gc_plgdata *plgdata;
207 // xslt filenames
208 const char *google2osync_file;
209 const char *osync2google_file;
211 // sync marker
212 const char *timestamp_name;
213 char *timestamp;
215 // sync handle
216 gcal_t handle;
218 // sink/format
219 OSyncObjFormat *format;
221 // XSLT context resource struct
222 struct xslt_resources *xslt_google2osync;
223 struct xslt_resources *xslt_osync2google;
226 struct gc_plgdata
228 char *url;
229 char *username;
230 char *password;
231 char *timezone;
232 char *xslt_path;
233 // libgcal resources
234 struct gc_gdata cal;
235 struct gc_gdata cont;
238 static void free_gdata(struct gc_gdata *gdata)
240 if( gdata->timestamp )
241 free(gdata->timestamp);
242 if( gdata->handle )
243 gcal_delete(gdata->handle);
244 if( gdata->format)
245 osync_objformat_unref(gdata->format);
246 if( gdata->xslt_google2osync )
247 xslt_delete(gdata->xslt_google2osync);
248 if( gdata->xslt_osync2google )
249 xslt_delete(gdata->xslt_osync2google);
252 static void free_plg(struct gc_plgdata *plgdata)
254 free_gdata(&plgdata->cal);
255 free_gdata(&plgdata->cont);
257 if (plgdata->xslt_path)
258 free(plgdata->xslt_path);
259 if (plgdata->timezone)
260 free(plgdata->timezone);
261 if (plgdata->url)
262 xmlFree(plgdata->url);
263 if (plgdata->username)
264 xmlFree(plgdata->username);
265 if (plgdata->password)
266 xmlFree(plgdata->password);
267 g_free(plgdata);
270 static void gc_connect(OSyncObjTypeSink *sink, OSyncPluginInfo *info,
271 OSyncContext *ctx, void *data)
273 osync_trace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, data, info, ctx);
274 int result;
275 struct gc_gdata *gdata = data;
276 OSyncError *error = NULL;
277 char buffer[512];
278 strcpy(buffer, "");
280 result = gcal_get_authentication(gdata->handle,
281 gdata->plgdata->username,
282 gdata->plgdata->password);
283 if (result == -1)
284 goto error;
286 // google -> osync
287 snprintf(buffer, sizeof(buffer) - 1, "%s%s",
288 gdata->plgdata->xslt_path,
289 gdata->google2osync_file);
290 if ((result = xslt_initialize(gdata->xslt_google2osync, buffer)))
291 goto error;
292 osync_trace(TRACE_INTERNAL, "loaded xslt: %s", buffer);
294 // osync -> google
295 snprintf(buffer, sizeof(buffer) - 1, "%s%s",
296 gdata->plgdata->xslt_path,
297 gdata->osync2google_file);
298 if ((result = xslt_initialize(gdata->xslt_osync2google, buffer)))
299 goto error;
300 osync_trace(TRACE_INTERNAL, "loaded xslt: %s", buffer);
302 osync_context_report_success(ctx);
303 osync_trace(TRACE_EXIT, "%s", __func__);
304 return;
306 error:
307 osync_trace(TRACE_INTERNAL, "Failed to load stylesheet: '%s'", buffer);
308 osync_error_set(&error, OSYNC_ERROR_GENERIC,
309 "Unable load stylesheet data: '%s'", buffer);
310 osync_context_report_osyncerror(ctx, error);
313 static void gc_get_changes_calendar(OSyncObjTypeSink *sink,
314 OSyncPluginInfo *info, OSyncContext *ctx,
315 osync_bool slow_sync, void *data)
317 osync_trace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, data, info, ctx);
318 struct gc_gdata *gdata = data;
319 OSyncError *error = NULL;
320 OSyncData *odata = NULL;
321 OSyncChange *chg = NULL;
322 int result = 0, i;
323 char *timestamp = NULL, *msg = NULL;
324 const char *raw_xml = NULL;
325 char *seen = NULL;
326 OSyncError *state_db_error = NULL;
327 OSyncSinkStateDB *state_db = NULL;
328 gcal_event_t event;
329 struct gcal_event_array all_events;
331 state_db = osync_objtype_sink_get_state_db(sink);
332 if( !state_db )
333 goto error;
335 timestamp = osync_sink_state_get(state_db, gdata->timestamp_name,
336 &state_db_error);
337 if (!timestamp) {
338 msg = "gcalendar: Anchor returned is NULL!";
339 goto error;
342 osync_trace(TRACE_INTERNAL, "timestamp is: '%s'\n", timestamp);
344 if (slow_sync || strlen(timestamp) == 0) {
345 osync_trace(TRACE_INTERNAL, "\n\t\tgcal: slow sync, or first time\n");
346 result = gcal_get_events(gdata->handle, &all_events);
348 } else {
349 result = gcal_get_updated_events(gdata->handle, &all_events,
350 timestamp);
353 if (result) {
354 msg = "Failed getting events!";
355 goto error;
358 osync_trace(TRACE_INTERNAL, "gcalendar: got them all!\n");
359 if (all_events.length == 0) {
360 osync_trace(TRACE_INTERNAL, "gcalendar: no changes...\n");
361 goto exit;
362 } else {
363 osync_trace(TRACE_INTERNAL, "gcalendar: changes count: %d\n",
364 all_events.length);
367 // Calendar returns most recently updated event as first element
368 for (i = 0; i < all_events.length; ++i) {
369 // cleanup for a fresh run
370 if (seen) {
371 osync_free(seen);
372 seen = NULL;
375 // grab the next event object
376 event = gcal_event_element(&all_events, i);
377 if (!event) {
378 osync_trace(TRACE_INTERNAL, "Cannot access updated event %d", i);
379 goto error;
382 // save first timestamp as new "done" mark
383 if (i == 0) {
384 if (gdata->timestamp)
385 free(gdata->timestamp);
386 gdata->timestamp = strdup(gcal_event_get_updated(event));
387 if (!gdata->timestamp) {
388 msg = "Failed copying event timestamp!\n";
389 goto error;
393 // are we done yet? libgcal includes the entry with the
394 // given timestamp, so if the timestamp of this event
395 // is <= to the timestamp we asked for, then we're done
396 if( !slow_sync && timestamp_cmp(gcal_event_get_updated(event), timestamp) <= 0 )
397 break;
399 osync_trace(TRACE_INTERNAL, "gevent: timestamp:%s\tevent:%s\n",
400 timestamp, gcal_event_get_updated(event));
402 // grab ID for current change... this is a Google URL
403 // the edit_url and etag are required later for modification,
404 // so save them in the state_db as the "seen" marker
405 const char *url = gcal_event_get_url(event);
406 const char *etag = gcal_event_get_etag(event);
408 // check state_db for id to see if we've seen this
409 // one before
410 seen = osync_sink_state_get(state_db, url, &state_db_error);
412 // determine changetype - we do not use osync_hashtable here
413 // because I believe that requires us to download all
414 // events in order to feed the timestamp to the hashtable
415 // function... hashtable is more suited to a local access,
416 // instead of internet access.
417 OSyncChangeType ct = OSYNC_CHANGE_TYPE_UNKNOWN;
418 if( gcal_event_is_deleted(event) ) {
419 ct = OSYNC_CHANGE_TYPE_DELETED;
421 // remember this item as deleted
422 if( !osync_sink_state_set(state_db, url, "", &state_db_error) ) {
423 msg = "Error setting state_db with url";
424 goto error;
426 if( slow_sync || !seen || strlen(seen) == 0 ) {
427 // in slow sync mode, we don't care about
428 // deleted objects
429 continue;
432 else {
433 if( !slow_sync && seen && strlen(seen) > 0 ) {
434 // we've seen this object before
435 ct = OSYNC_CHANGE_TYPE_MODIFIED;
437 else {
438 ct = OSYNC_CHANGE_TYPE_ADDED;
441 // the etag will have changed for MODIFIED, and
442 // it's a new item if ADDED, so save the url/etag
443 // string either way
444 // FIXME - should perhaps set this only after
445 // success, such as in the done() plugin call
446 if( !osync_sink_state_set(state_db, url, etag, &state_db_error) ) {
447 msg = "Error setting state_db with url/etag";
448 goto error;
452 // create change object
453 chg = osync_change_new(&error);
454 if( !chg )
455 goto cleanup;
457 // setup the change
458 osync_change_set_uid(chg, url);
459 osync_change_set_hash(chg, gcal_event_get_updated(event));
460 osync_change_set_changetype(chg, ct);
462 // fill in the data
463 if( ct != OSYNC_CHANGE_TYPE_DELETED ) {
464 raw_xml = gcal_event_get_xml(event);
465 if( xslt_transform(gdata->xslt_google2osync, raw_xml) ) {
466 osync_change_unref(chg);
467 goto error;
470 raw_xml = (char*) gdata->xslt_google2osync->xml_str;
471 odata = osync_data_new(strdup(raw_xml),
472 strlen(raw_xml),
473 gdata->format, &error);
474 if( !odata ) {
475 osync_change_unref(chg);
476 goto cleanup;
479 else {
480 // deleted changes need empty data sets
481 odata = osync_data_new(NULL, 0, gdata->format, &error);
482 if( !odata ) {
483 osync_change_unref(chg);
484 goto cleanup;
488 osync_data_set_objtype(odata,
489 osync_objtype_sink_get_name(sink));
490 osync_change_set_data(chg, odata);
491 osync_data_unref(odata);
493 osync_context_report_change(ctx, chg);
494 osync_change_unref(chg);
498 exit:
499 osync_context_report_success(ctx);
500 goto cleanup;
502 error:
503 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "%s", msg);
505 cleanup:
506 osync_error_unref(&error);
507 gcal_cleanup_events(&all_events);
509 // osync_sink_state_get uses osync_strdup
510 osync_free(timestamp);
512 if (seen)
513 osync_free(seen);
517 static void gc_get_changes_contact(OSyncObjTypeSink *sink,
518 OSyncPluginInfo *info, OSyncContext *ctx,
519 osync_bool slow_sync, void *data)
521 osync_trace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, data, info, ctx);
522 struct gc_gdata *gdata = data;
523 OSyncError *error = NULL;
524 OSyncData *odata = NULL;
525 OSyncChange *chg = NULL;
526 int result = 0, i;
527 char *timestamp = NULL, *msg = NULL;
528 const char *raw_xml = NULL;
529 char *seen = NULL;
530 OSyncError *state_db_error = NULL;
531 OSyncSinkStateDB *state_db = NULL;
532 gcal_contact_t contact;
533 struct gcal_contact_array all_contacts;
535 state_db = osync_objtype_sink_get_state_db(sink);
536 if( !state_db )
537 goto error;
539 timestamp = osync_sink_state_get(state_db, gdata->timestamp_name,
540 &state_db_error);
541 if (!timestamp) {
542 msg = "gcontact: Anchor returned is NULL!";
543 goto error;
546 osync_trace(TRACE_INTERNAL, "timestamp is: '%s'\n", timestamp);
548 if (slow_sync || strlen(timestamp) == 0) {
549 osync_trace(TRACE_INTERNAL, "\n\t\tgcont: slow sync, or first time\n");
550 result = gcal_get_contacts(gdata->handle, &all_contacts);
552 } else {
553 gcal_deleted(gdata->handle, SHOW);
554 result = gcal_get_updated_contacts(gdata->handle, &all_contacts,
555 timestamp);
558 if (result) {
559 msg = "Failed getting contacts!";
560 goto error;
563 osync_trace(TRACE_INTERNAL, "gcontact: got them all!\n");
564 if (all_contacts.length == 0) {
565 osync_trace(TRACE_INTERNAL, "gcontact: no changes...\n");
566 goto exit;
567 } else
568 osync_trace(TRACE_INTERNAL, "gcontact: changes count: %d\n",
569 all_contacts.length);
571 // Contacts returns most recently updated entry as last element
572 for (i = 0; i < all_contacts.length; ++i) {
573 // cleanup for a fresh run
574 if (seen) {
575 osync_free(seen);
576 seen = NULL;
579 // grab the next event object
580 contact = gcal_contact_element(&all_contacts, i);
581 if (!contact) {
582 osync_trace(TRACE_INTERNAL, "Cannot access updated contact %d", i);
583 goto error;
586 // save first timestamp as new "done" mark
587 if (i == 0) {
588 if (gdata->timestamp)
589 free(gdata->timestamp);
590 gdata->timestamp = strdup(gcal_contact_get_updated(contact));
591 if (!gdata->timestamp) {
592 msg = "Failed copying contact timestamp!\n";
593 goto error;
597 // are we done yet? libgcal includes the entry with the
598 // given timestamp, so if the timestamp of this contact
599 // is <= to the timestamp we asked for, then we're done
600 if( !slow_sync && timestamp_cmp(gcal_contact_get_updated(contact), timestamp) <= 0 )
601 break;
603 osync_trace(TRACE_INTERNAL, "gcontact: timestamp:%s\tcontact:%s\n",
604 timestamp, gcal_contact_get_updated(contact));
606 // grab ID for current change... this is a Google URL
607 // the edit_url and etag are required later for modification,
608 // so save them in the state_db as the "seen" marker
609 const char *url = gcal_contact_get_url(contact);
610 const char *etag = gcal_contact_get_etag(contact);
612 // check state_db for id to see if we've seen this
613 // one before
614 seen = osync_sink_state_get(state_db, url, &state_db_error);
616 // determine changetype - we do not use osync_hashtable here
617 // because I believe that requires us to download all
618 // contacts in order to feed the timestamp to the hashtable
619 // function... hashtable is more suited to a local access,
620 // instead of internet access.
621 OSyncChangeType ct = OSYNC_CHANGE_TYPE_UNKNOWN;
622 if( gcal_contact_is_deleted(contact) ) {
623 ct = OSYNC_CHANGE_TYPE_DELETED;
625 // remember this item as deleted
626 if( !osync_sink_state_set(state_db, url, "", &state_db_error) ) {
627 msg = "Error setting state_db with url";
628 goto error;
630 if( slow_sync || !seen || strlen(seen) == 0 ) {
631 // in slow sync mode, we don't care about
632 // deleted objects
633 continue;
636 else {
637 if( !slow_sync && seen && strlen(seen) > 0 ) {
638 // we've seen this object before
639 ct = OSYNC_CHANGE_TYPE_MODIFIED;
641 else {
642 ct = OSYNC_CHANGE_TYPE_ADDED;
645 // the etag will have changed for MODIFIED, and
646 // it's a new item if ADDED, so save the url/etag
647 // string either way
648 // FIXME - should perhaps set this only after
649 // success, such as in the done() plugin call
650 if( !osync_sink_state_set(state_db, url, etag, &state_db_error) ) {
651 msg = "Error setting state_db with url/etag";
652 goto error;
656 // create change object
657 chg = osync_change_new(&error);
658 if( !chg )
659 goto cleanup;
661 // setup the change
662 osync_change_set_uid(chg, url);
663 osync_change_set_hash(chg, gcal_contact_get_updated(contact));
664 osync_change_set_changetype(chg, ct);
666 // fill in the data
667 if( ct != OSYNC_CHANGE_TYPE_DELETED ) {
668 raw_xml = gcal_contact_get_xml(contact);
669 if( xslt_transform(gdata->xslt_google2osync, raw_xml) ) {
670 osync_change_unref(chg);
671 goto error;
674 raw_xml = (char*) gdata->xslt_google2osync->xml_str;
675 odata = osync_data_new(strdup(raw_xml),
676 strlen(raw_xml),
677 gdata->format, &error);
678 if( !odata ) {
679 osync_change_unref(chg);
680 goto cleanup;
683 else {
684 // deleted changes need empty data sets
685 odata = osync_data_new(NULL, 0, gdata->format, &error);
686 if( !odata ) {
687 osync_change_unref(chg);
688 goto cleanup;
692 osync_data_set_objtype(odata,
693 osync_objtype_sink_get_name(sink));
694 osync_change_set_data(chg, odata);
695 osync_data_unref(odata);
697 osync_context_report_change(ctx, chg);
698 osync_change_unref(chg);
701 exit:
702 osync_context_report_success(ctx);
703 goto cleanup;
705 error:
706 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "%s", msg);
708 cleanup:
709 osync_error_unref(&error);
710 gcal_cleanup_contacts(&all_contacts);
712 // osync_sink_state_get uses osync_strdup
713 osync_free(timestamp);
715 if (seen)
716 osync_free(seen);
719 static void gc_commit_change_calendar(OSyncObjTypeSink *sink,
720 OSyncPluginInfo *info, OSyncContext *ctx,
721 OSyncChange *change, void *data)
723 osync_trace(TRACE_ENTRY, "%s(%p, %p, %p, %p, %p)", __func__, sink,
724 info, ctx, change, data);
725 osync_trace(TRACE_INTERNAL, "hello, from calendar!\n");
726 struct gc_gdata *gdata = data;
727 gcal_event_t event = NULL;
728 unsigned int size;
729 int result = 55555; // something odd for the logs
730 char *osync_xml = NULL, *msg = NULL, *raw_xml = NULL, *updated_event = NULL;
731 char *etag = NULL;
732 OSyncData *odata = NULL;
733 OSyncError *state_db_error = NULL;
734 OSyncSinkStateDB *state_db = NULL;
736 state_db = osync_objtype_sink_get_state_db(sink);
737 if( !state_db ) {
738 msg = "Cannot start state_db!";
739 goto error;
742 odata = osync_change_get_data(change);
743 if( !odata ) {
744 msg = "Cannot get raw data from change obj!\n";
745 goto error;
748 // transform data, only for ADD / MODIFY
749 if( osync_change_get_changetype(change) != OSYNC_CHANGE_TYPE_DELETED ) {
750 osync_data_get_data(odata, &osync_xml, &size);
751 if( !osync_xml ) {
752 msg = "Failed getting xml from xmlobj!\n";
753 goto error;
756 // Convert to gdata format
757 result = xslt_transform(gdata->xslt_osync2google, osync_xml);
758 if( result ) {
759 msg = "Failed converting from osync xmlevent to gcalendar\n";
760 osync_trace(TRACE_INTERNAL, "--- osync_uid: %s",
761 osync_change_get_uid(change));
762 osync_trace(TRACE_INTERNAL,"Failed converting from osync xmlevent to gcalendar: %u, %s",
763 size, osync_xml);
764 goto error;
767 osync_trace(TRACE_INTERNAL, "--- transformed xml: %s",
768 (char*) gdata->xslt_osync2google->xml_str);
770 raw_xml = vtime2gtime((char*)gdata->xslt_osync2google->xml_str);
771 osync_trace(TRACE_INTERNAL, "--- gtime adjusted: %s", raw_xml);
774 // check state_db for id to see if we've seen this
775 // one before, and grab the etag if so
776 if( osync_change_get_uid(change) ) {
777 etag = osync_sink_state_get(state_db,
778 osync_change_get_uid(change), &state_db_error);
781 switch( osync_change_get_changetype(change) )
783 case OSYNC_CHANGE_TYPE_ADDED:
784 result = gcal_add_xmlentry(gdata->handle, raw_xml,
785 &updated_event);
786 if( result == -1 ) {
787 msg = "Failed adding new event!\n";
788 osync_trace(TRACE_INTERNAL, "Failed adding new event! HTTP code: %d, %s, %s\n",
789 gcal_status_httpcode(gdata->handle),
790 gcal_status_msg(gdata->handle),
791 gcal_access_buffer(gdata->handle));
792 goto error;
795 event = gcal_event_new(updated_event);
796 if( !event ) {
797 msg = "Failed recovering updated fields!\n";
798 goto error;
801 osync_trace(TRACE_INTERNAL, "New event added: url = %s etag = %s",
802 gcal_event_get_url(event),
803 gcal_event_get_etag(event));
805 // mark this as "seen"
806 if( !osync_sink_state_set(state_db, gcal_event_get_url(event), gcal_event_get_etag(event), &state_db_error) ) {
807 msg = "Error setting added state";
808 goto error;
811 // tell opensync to store the new ID
812 osync_change_set_uid(change, gcal_event_get_url(event));
813 break;
815 case OSYNC_CHANGE_TYPE_MODIFIED:
816 if( !etag ) {
817 msg = "Trying to modify an unknown entry!";
818 goto error;
821 result = gcal_update_xmlentry(gdata->handle, raw_xml,
822 &updated_event, osync_change_get_uid(change), etag);
823 if( result == -1 ) {
824 msg = "Failed editing event!\n";
825 osync_trace(TRACE_INTERNAL, "Failed editing event: (etag: %s). HTTP code: %d, %s, %s\n",
826 etag,
827 gcal_status_httpcode(gdata->handle),
828 gcal_status_msg(gdata->handle),
829 gcal_access_buffer(gdata->handle));
830 goto error;
833 event = gcal_event_new(updated_event);
834 if( !event ) {
835 msg = "Failed recovering updated fields!\n";
836 goto error;
839 osync_trace(TRACE_INTERNAL,"Modified event: url = %s etag = %s",
840 gcal_event_get_url(event),
841 gcal_event_get_etag(event));
843 // make sure that new ID is the same as existing UID
844 if( strcmp(osync_change_get_uid(change), gcal_event_get_url(event)) != 0 ) {
845 msg = "Opensync UID != modified event ID";
846 osync_trace(TRACE_INTERNAL, "Opensync UID != modified event ID: uid = %s, event_id = %s, updated_event = %s",
847 osync_change_get_uid(change),
848 gcal_event_get_url(event),
849 updated_event);
850 goto error;
853 // mark this as "seen"
854 if( !osync_sink_state_set(state_db, gcal_event_get_url(event), gcal_event_get_etag(event), &state_db_error) ) {
855 msg = "Error setting modified state";
856 goto error;
858 break;
860 case OSYNC_CHANGE_TYPE_DELETED:
861 // create empty event, set the edit_url, and then delete
862 event = gcal_event_new(NULL);
863 result = gcal_event_set_url(event, osync_change_get_uid(change));
864 if( result == -1 ) {
865 msg = "Failed setting url for event delete\n";
866 goto error;
869 result = gcal_erase_event(gdata->handle, event);
870 if( result == -1 ) {
871 msg = "Failed deleting event!\n";
872 osync_trace(TRACE_INTERNAL, "Failed deleting event! HTTP code: %d, %s, %s\n",
873 gcal_status_httpcode(gdata->handle),
874 gcal_status_msg(gdata->handle),
875 gcal_access_buffer(gdata->handle));
876 goto error;
879 // mark this as "unseen"
880 if( !osync_sink_state_set(state_db,
881 osync_change_get_uid(change), "", &state_db_error) )
883 msg = "Error setting modified state";
884 goto error;
886 break;
888 default:
889 msg = "Unknown change type";
890 goto error;
893 if( event && gcal_event_get_updated(event) ) {
894 // update the timestamp
895 if( gdata->timestamp ) {
896 // only if newer
897 if( timestamp_cmp(gcal_event_get_updated(event), gdata->timestamp) > 0 ) {
898 free(gdata->timestamp);
899 gdata->timestamp = strdup(gcal_event_get_updated(event));
902 else {
903 gdata->timestamp = strdup(gcal_event_get_updated(event));
906 // error check
907 if (!gdata->timestamp) {
908 msg = "Failed copying contact timestamp!\n";
909 goto error;
913 osync_context_report_success(ctx);
914 osync_trace(TRACE_EXIT, "%s", __func__);
916 goto cleanup;
918 error:
919 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "%s", msg);
920 osync_trace(TRACE_EXIT, "%s:%sResult code: %d", __func__, msg, result);
922 cleanup:
923 if (updated_event)
924 free(updated_event);
925 if (event)
926 gcal_event_delete(event);
927 if (raw_xml)
928 free(raw_xml);
929 if (etag)
930 free(etag);
933 static void gc_commit_change_contact(OSyncObjTypeSink *sink,
934 OSyncPluginInfo *info, OSyncContext *ctx,
935 OSyncChange *change, void *data)
937 osync_trace(TRACE_ENTRY, "%s(%p, %p, %p, %p, %p)", __func__, sink,
938 info, ctx, change, data);
939 osync_trace(TRACE_INTERNAL, "hello, from contacts!\n");
941 struct gc_gdata *gdata = data;
942 gcal_contact_t contact = NULL;
943 unsigned int size;
944 int result;
945 char *osync_xml = NULL, *msg = NULL, *raw_xml = NULL, *updated_contact = NULL;
946 OSyncData *odata = NULL;
948 if (!(odata = osync_change_get_data(change))) {
949 msg = "Cannot get raw data from change obj!\n";
950 goto error;
953 osync_data_get_data(odata, &osync_xml, &size);
954 if (!osync_xml) {
955 msg = "Failed getting xml from xmlobj!\n";
956 goto error;
959 // Convert to gdata format
960 if ((result = xslt_transform(gdata->xslt_google2osync, osync_xml))) {
961 msg = "Failed converting from osync xmlcontact to gcontact\n";
962 goto error;
964 raw_xml = vtime2gtime( (char*) gdata->xslt_google2osync->xml_str );
966 osync_trace(TRACE_INTERNAL, "osync: %s\ngcont: %s\n\n", osync_xml, raw_xml);
968 switch (osync_change_get_changetype(change)) {
969 case OSYNC_CHANGE_TYPE_ADDED:
970 result = gcal_add_xmlentry(gdata->handle, raw_xml, &updated_contact);
971 if (result == -1) {
972 msg = "Failed adding new contact!\n";
973 result = gcal_status_httpcode(gdata->handle);
974 goto error;
977 if (!(contact = gcal_contact_new(updated_contact))) {
978 msg = "Failed recovering updated fields!\n";
979 goto error;
981 break;
983 case OSYNC_CHANGE_TYPE_MODIFIED:
984 result = gcal_update_xmlentry(gdata->handle,
985 raw_xml, &updated_contact, NULL, NULL);
986 if (result == -1) {
987 msg = "Failed editing contact!\n";
988 goto error;
991 if (!(contact = gcal_contact_new(updated_contact))) {
992 msg = "Failed recovering updated fields!\n";
993 goto error;
995 break;
997 case OSYNC_CHANGE_TYPE_DELETED:
998 result = gcal_erase_xmlentry(gdata->handle, raw_xml);
999 if (result == -1) {
1000 msg = "Failed deleting contact!\n";
1001 goto error;
1003 break;
1005 default:
1006 osync_context_report_error(ctx, OSYNC_ERROR_NOT_SUPPORTED,
1007 "Unknown change type");
1008 goto error;
1009 break;
1012 if (updated_contact)
1013 free(updated_contact);
1015 if (contact) {
1016 // update the timestamp
1017 if (gdata->timestamp)
1018 free(gdata->timestamp);
1019 gdata->timestamp = strdup(gcal_contact_get_updated(contact));
1020 if (!gdata->timestamp) {
1021 msg = "Failed copying contact timestamp!\n";
1022 goto error;
1025 // FIXME: not sure if this works
1026 // Inform the new ID
1027 osync_change_set_uid(change, gcal_contact_get_url(contact));
1028 gcal_contact_delete(contact);
1031 //free(osync_xml);
1033 osync_context_report_success(ctx);
1034 osync_trace(TRACE_EXIT, "%s", __func__);
1036 return;
1038 error:
1039 if (updated_contact)
1040 free(updated_contact);
1041 if (raw_xml)
1042 free(raw_xml);
1044 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "%s", msg);
1045 osync_trace(TRACE_EXIT, "%s:%sHTTP code: %d", __func__, msg, result);
1048 static void gc_sync_done(OSyncObjTypeSink *sink, OSyncPluginInfo *info,
1049 OSyncContext *ctx, void *data)
1051 osync_trace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, data, info, ctx);
1052 struct gc_gdata *gdata = data;
1053 OSyncError *state_db_error;
1055 if( gdata->handle && gdata->timestamp ) {
1056 osync_trace(TRACE_INTERNAL, "query updated timestamp: %s: %s\n",
1057 gdata->timestamp_name, gdata->timestamp);
1058 osync_sink_state_set(osync_objtype_sink_get_state_db(sink),
1059 gdata->timestamp_name, gdata->timestamp,
1060 &state_db_error);
1063 osync_context_report_success(ctx);
1066 static void gc_disconnect(OSyncObjTypeSink *sink, OSyncPluginInfo *info,
1067 OSyncContext *ctx, void *data)
1069 osync_trace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, data, info, ctx);
1070 osync_context_report_success(ctx);
1071 osync_trace(TRACE_EXIT, "%s", __func__);
1075 /////////////////////////////////////////////////////////////////////////////
1076 // Plugin API
1078 static void *gc_initialize(OSyncPlugin *plugin,
1079 OSyncPluginInfo *info,
1080 OSyncError **error)
1082 osync_trace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, plugin, info, error);
1083 struct gc_plgdata *plgdata;
1084 OSyncPluginConfig *config;
1085 OSyncList *resources;
1086 OSyncList *r;
1088 plgdata = osync_try_malloc0(sizeof(struct gc_plgdata), error);
1089 config = osync_plugin_info_get_config(info);
1090 if( (!plgdata) || (!config) ) {
1091 osync_error_set(error, OSYNC_ERROR_GENERIC,
1092 "Unable to get config data.");
1093 goto error_freeplg;
1096 // set parent pointers
1097 plgdata->cal.plgdata = plgdata;
1098 plgdata->cont.plgdata = plgdata;
1100 // set xslt filenames
1101 plgdata->cal.timestamp_name = "cal_timestamp";
1102 plgdata->cal.google2osync_file = "gcal2osync.xslt";
1103 plgdata->cal.osync2google_file = "osync2gcal.xslt";
1104 plgdata->cont.timestamp_name = "cont_timestamp";
1105 plgdata->cont.google2osync_file = "gcont2osync.xslt";
1106 plgdata->cont.osync2google_file = "osync2gcont.xslt";
1108 // grab config dir
1109 plgdata->xslt_path = strdup(osync_plugin_get_default_configdir());
1110 if( !plgdata->xslt_path )
1111 goto error_freeplg;
1113 // create a gcal handle for each objtype available
1114 resources = osync_plugin_config_get_resources(config);
1115 for( r = resources; r; r = r->next ) {
1116 osync_trace(TRACE_INTERNAL, "field: %s\n",
1117 osync_plugin_resource_get_objtype(r->data));
1119 if( !strcmp(osync_plugin_resource_get_objtype(r->data), "event") ) {
1120 plgdata->cal.handle = gcal_new(GCALENDAR);
1121 if( !plgdata->cal.handle )
1122 goto error_freeplg;
1123 else {
1124 osync_trace(TRACE_INTERNAL,
1125 "\tcreated calendar obj!\n");
1126 gcal_set_store_xml(plgdata->cal.handle, 1);
1130 if( !strcmp(osync_plugin_resource_get_objtype(r->data), "contact") ) {
1131 plgdata->cont.handle = gcal_new(GCONTACT);
1132 if( !plgdata->cont.handle )
1133 goto error_freeplg;
1134 else {
1135 osync_trace(TRACE_INTERNAL,
1136 "\tcreated contact obj!\n");
1137 gcal_set_store_xml(plgdata->cont.handle, 1);
1144 // Fetch username
1145 OSyncPluginAuthentication *optauth = NULL;
1146 optauth = osync_plugin_config_get_authentication(config);
1147 if( osync_plugin_authentication_option_is_supported(optauth,
1148 OSYNC_PLUGIN_AUTHENTICATION_USERNAME) ) {
1149 const char *user =
1150 osync_plugin_authentication_get_username(optauth);
1151 if( !user )
1152 goto error_freeplg;
1154 plgdata->username = strdup(user);
1155 if( !plgdata->username )
1156 goto error_freeplg;
1158 else {
1159 goto error_freeplg;
1162 // Fetch password
1163 if( osync_plugin_authentication_option_is_supported(optauth,
1164 OSYNC_PLUGIN_AUTHENTICATION_PASSWORD) ) {
1165 const char *pass =
1166 osync_plugin_authentication_get_password(optauth);
1167 if( !pass )
1168 goto error_freeplg;
1170 plgdata->password = strdup(pass);
1171 if( !plgdata->password )
1172 goto error_freeplg;
1174 else {
1175 goto error_freeplg;
1178 // TODO: get proxy/calendar title/resources/etc
1181 // Register calendar sink
1182 OSyncObjTypeSink *sink = NULL;
1183 sink = osync_plugin_info_find_objtype(info, "event");
1184 if( !sink ) {
1185 osync_trace(TRACE_ERROR, "%s", "Failed to find objtype event!");
1187 if( sink && osync_objtype_sink_is_enabled(sink) && plgdata->cal.handle ) {
1189 osync_trace(TRACE_INTERNAL, "\tcreating calendar sink...\n");
1190 OSyncFormatEnv *formatenv = osync_plugin_info_get_format_env(info);
1191 plgdata->cal.format = osync_format_env_find_objformat(formatenv, "xmlformat-event-doc");
1192 if (!plgdata->cal.format) {
1193 osync_trace(TRACE_ERROR, "%s", "Failed to find objformat xmlformat-event!");
1194 goto error_freeplg;
1196 osync_objformat_ref(plgdata->cal.format);
1199 osync_objtype_sink_set_connect_func(sink, gc_connect);
1200 osync_objtype_sink_set_disconnect_func(sink, gc_disconnect);
1201 osync_objtype_sink_set_get_changes_func(sink,
1202 gc_get_changes_calendar);
1203 osync_objtype_sink_set_commit_func(sink,
1204 gc_commit_change_calendar);
1205 osync_objtype_sink_set_sync_done_func(sink, gc_sync_done);
1208 osync_objtype_sink_set_userdata(sink, &plgdata->cal);
1209 osync_objtype_sink_enable_state_db(sink, TRUE);
1216 // Register contact sink
1217 sink = NULL;
1218 sink = osync_plugin_info_find_objtype(info, "contact");
1219 if( !sink ) {
1220 osync_trace(TRACE_ERROR, "%s", "Failed to find objtype contact!");
1222 if( sink && osync_objtype_sink_is_enabled(sink) && plgdata->cont.handle ) {
1224 osync_trace(TRACE_INTERNAL, "\tcreating contact sink...\n");
1225 OSyncFormatEnv *formatenv = osync_plugin_info_get_format_env(info);
1226 plgdata->cont.format = osync_format_env_find_objformat(formatenv, "xmlformat-contact-doc");
1227 if (!plgdata->cont.format) {
1228 osync_trace(TRACE_ERROR, "%s", "Failed to find objformat xmlformat-contact!");
1229 goto error_freeplg;
1231 osync_objformat_ref(plgdata->cont.format);
1234 osync_objtype_sink_set_connect_func(sink, gc_connect);
1235 osync_objtype_sink_set_disconnect_func(sink, gc_disconnect);
1236 osync_objtype_sink_set_get_changes_func(sink,
1237 gc_get_changes_contact);
1238 osync_objtype_sink_set_commit_func(sink,
1239 gc_commit_change_contact);
1240 osync_objtype_sink_set_sync_done_func(sink, gc_sync_done);
1243 osync_objtype_sink_set_userdata(sink, &plgdata->cont);
1244 osync_objtype_sink_enable_state_db(sink, TRUE);
1248 if( plgdata->cal.handle ) {
1249 plgdata->cal.xslt_google2osync = xslt_new();
1250 plgdata->cal.xslt_osync2google = xslt_new();
1251 if (!plgdata->cal.xslt_google2osync || !plgdata->cal.xslt_osync2google)
1252 goto error_freeplg;
1253 else
1254 osync_trace(TRACE_INTERNAL, "\tsucceed creating xslt_gcal!\n");
1257 if( plgdata->cont.handle ) {
1258 plgdata->cont.xslt_google2osync = xslt_new();
1259 plgdata->cont.xslt_osync2google = xslt_new();
1260 if (!plgdata->cont.xslt_google2osync || !plgdata->cont.xslt_osync2google)
1261 goto error_freeplg;
1262 else
1263 osync_trace(TRACE_INTERNAL, "\tsucceed creating xslt_gcont!\n");
1266 osync_trace(TRACE_EXIT, "%s", __func__);
1268 return plgdata;
1270 error_freeplg:
1271 if (plgdata)
1272 free_plg(plgdata);
1273 osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
1274 return NULL;
1277 static osync_bool gc_discover(OSyncPluginInfo *info, void *data, OSyncError **error)
1279 osync_trace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, data, info, error);
1281 OSyncList *sinks = osync_plugin_info_get_objtype_sinks(info);
1282 OSyncList *s = sinks;
1283 for( ; s; s = s->next ) {
1284 OSyncObjTypeSink *sink = (OSyncObjTypeSink*) s->data;
1285 osync_objtype_sink_set_available(sink, TRUE);
1288 OSyncVersion *version = osync_version_new(error);
1289 osync_version_set_plugin(version, "google-data");
1290 osync_plugin_info_set_version(info, version);
1291 osync_version_unref(version);
1293 osync_trace(TRACE_EXIT, "%s", __func__);
1294 return TRUE;
1297 static void gc_finalize(void *data)
1299 osync_trace(TRACE_ENTRY, "%s(%p)", __func__, data);
1300 struct gc_plgdata *plgdata = data;
1302 free_plg(plgdata);
1303 osync_trace(TRACE_EXIT, "%s", __func__);
1306 osync_bool get_sync_info(OSyncPluginEnv *env, OSyncError **error)
1308 osync_trace(TRACE_ENTRY, "%s(%p, %p)", __func__, env, error);
1309 OSyncPlugin *plugin = osync_plugin_new(error);
1310 if( !plugin )
1311 goto error;
1313 osync_plugin_set_name(plugin, "google-data");
1314 osync_plugin_set_longname(plugin, "Google calendar/plugin");
1315 osync_plugin_set_description(plugin,
1316 "Google calendar and contacts plugin");
1318 osync_plugin_set_initialize(plugin, gc_initialize);
1319 osync_plugin_set_finalize(plugin, gc_finalize);
1320 osync_plugin_set_discover(plugin, gc_discover);
1321 osync_plugin_set_start_type(plugin, OSYNC_START_TYPE_PROCESS);
1323 if( !osync_plugin_env_register_plugin(env, plugin, error) )
1324 goto error;
1325 osync_plugin_unref(plugin);
1327 osync_trace(TRACE_EXIT, "%s", __func__);
1328 return TRUE;
1330 error:
1331 osync_trace(TRACE_EXIT_ERROR, "Unable to register: %s",
1332 osync_error_print(error));
1333 return FALSE;
1336 int get_version(void)
1338 return 1;