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
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>
40 #include <libxml/tree.h>
46 #include <sys/types.h>
49 #include <gcal_status.h>
50 #include <gcalendar.h>
54 #include "timestamp.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
;
83 if (!timestamp1
&& !timestamp2
)
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
)
103 else if (t_first
> t_second
)
105 else if (t_first
< t_second
)
112 const char* findstr(const char *data
, const char *format
)
114 const char *match
= format
;
115 const char *start
= data
;
117 while( *data
&& *match
) {
123 matched
= isdigit(*data
);
126 matched
= (*data
== *match
);
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);
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?
169 // adjust the timestamp
170 target
[0] = match
[0];
171 target
[1] = match
[1];
172 target
[2] = match
[2];
173 target
[3] = match
[3];
175 target
[5] = match
[4];
176 target
[6] = match
[5];
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];
184 target
[14] = match
[11];
185 target
[15] = match
[12];
187 target
[17] = match
[13];
188 target
[18] = match
[14];
205 struct gc_plgdata
*plgdata
;
208 const char *google2osync_file
;
209 const char *osync2google_file
;
212 const char *timestamp_name
;
219 OSyncObjFormat
*format
;
221 // XSLT context resource struct
222 struct xslt_resources
*xslt_google2osync
;
223 struct xslt_resources
*xslt_osync2google
;
235 struct gc_gdata cont
;
238 static void free_gdata(struct gc_gdata
*gdata
)
240 if( gdata
->timestamp
)
241 free(gdata
->timestamp
);
243 gcal_delete(gdata
->handle
);
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
);
262 xmlFree(plgdata
->url
);
263 if (plgdata
->username
)
264 xmlFree(plgdata
->username
);
265 if (plgdata
->password
)
266 xmlFree(plgdata
->password
);
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
);
275 struct gc_gdata
*gdata
= data
;
276 OSyncError
*error
= NULL
;
280 result
= gcal_get_authentication(gdata
->handle
,
281 gdata
->plgdata
->username
,
282 gdata
->plgdata
->password
);
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
)))
292 osync_trace(TRACE_INTERNAL
, "loaded xslt: %s", buffer
);
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
)))
300 osync_trace(TRACE_INTERNAL
, "loaded xslt: %s", buffer
);
302 osync_context_report_success(ctx
);
303 osync_trace(TRACE_EXIT
, "%s", __func__
);
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
;
323 char *timestamp
= NULL
, *msg
= NULL
;
324 const char *raw_xml
= NULL
;
326 OSyncError
*state_db_error
= NULL
;
327 OSyncSinkStateDB
*state_db
= NULL
;
329 struct gcal_event_array all_events
;
331 state_db
= osync_objtype_sink_get_state_db(sink
);
335 timestamp
= osync_sink_state_get(state_db
, gdata
->timestamp_name
,
338 msg
= "gcalendar: Anchor returned is NULL!";
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
);
349 result
= gcal_get_updated_events(gdata
->handle
, &all_events
,
354 msg
= "Failed getting events!";
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");
363 osync_trace(TRACE_INTERNAL
, "gcalendar: changes count: %d\n",
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
375 // grab the next event object
376 event
= gcal_event_element(&all_events
, i
);
378 osync_trace(TRACE_INTERNAL
, "Cannot access updated event %d", i
);
382 // save first timestamp as new "done" mark
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";
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 )
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
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";
426 if( slow_sync
|| !seen
|| strlen(seen
) == 0 ) {
427 // in slow sync mode, we don't care about
433 if( !slow_sync
&& seen
&& strlen(seen
) > 0 ) {
434 // we've seen this object before
435 ct
= OSYNC_CHANGE_TYPE_MODIFIED
;
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
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";
452 // create change object
453 chg
= osync_change_new(&error
);
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
);
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
);
470 raw_xml
= (char*) gdata
->xslt_google2osync
->xml_str
;
471 odata
= osync_data_new(strdup(raw_xml
),
473 gdata
->format
, &error
);
475 osync_change_unref(chg
);
480 // deleted changes need empty data sets
481 odata
= osync_data_new(NULL
, 0, gdata
->format
, &error
);
483 osync_change_unref(chg
);
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
);
499 osync_context_report_success(ctx
);
503 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "%s", msg
);
506 osync_error_unref(&error
);
507 gcal_cleanup_events(&all_events
);
509 // osync_sink_state_get uses osync_strdup
510 osync_free(timestamp
);
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
;
527 char *timestamp
= NULL
, *msg
= NULL
;
528 const char *raw_xml
= 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
);
539 timestamp
= osync_sink_state_get(state_db
, gdata
->timestamp_name
,
542 msg
= "gcontact: Anchor returned is NULL!";
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
);
553 gcal_deleted(gdata
->handle
, SHOW
);
554 result
= gcal_get_updated_contacts(gdata
->handle
, &all_contacts
,
559 msg
= "Failed getting contacts!";
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");
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
579 // grab the next event object
580 contact
= gcal_contact_element(&all_contacts
, i
);
582 osync_trace(TRACE_INTERNAL
, "Cannot access updated contact %d", i
);
586 // save first timestamp as new "done" mark
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";
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 )
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
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";
630 if( slow_sync
|| !seen
|| strlen(seen
) == 0 ) {
631 // in slow sync mode, we don't care about
637 if( !slow_sync
&& seen
&& strlen(seen
) > 0 ) {
638 // we've seen this object before
639 ct
= OSYNC_CHANGE_TYPE_MODIFIED
;
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
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";
656 // create change object
657 chg
= osync_change_new(&error
);
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
);
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
);
674 raw_xml
= (char*) gdata
->xslt_google2osync
->xml_str
;
675 odata
= osync_data_new(strdup(raw_xml
),
677 gdata
->format
, &error
);
679 osync_change_unref(chg
);
684 // deleted changes need empty data sets
685 odata
= osync_data_new(NULL
, 0, gdata
->format
, &error
);
687 osync_change_unref(chg
);
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
);
702 osync_context_report_success(ctx
);
706 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "%s", msg
);
709 osync_error_unref(&error
);
710 gcal_cleanup_contacts(&all_contacts
);
712 // osync_sink_state_get uses osync_strdup
713 osync_free(timestamp
);
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
;
729 int result
= 55555; // something odd for the logs
730 char *osync_xml
= NULL
, *msg
= NULL
, *raw_xml
= NULL
, *updated_event
= 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
);
738 msg
= "Cannot start state_db!";
742 odata
= osync_change_get_data(change
);
744 msg
= "Cannot get raw data from change obj!\n";
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
);
752 msg
= "Failed getting xml from xmlobj!\n";
756 // Convert to gdata format
757 result
= xslt_transform(gdata
->xslt_osync2google
, osync_xml
);
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",
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
,
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
));
795 event
= gcal_event_new(updated_event
);
797 msg
= "Failed recovering updated fields!\n";
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";
811 // tell opensync to store the new ID
812 osync_change_set_uid(change
, gcal_event_get_url(event
));
815 case OSYNC_CHANGE_TYPE_MODIFIED
:
817 msg
= "Trying to modify an unknown entry!";
821 result
= gcal_update_xmlentry(gdata
->handle
, raw_xml
,
822 &updated_event
, osync_change_get_uid(change
), etag
);
824 msg
= "Failed editing event!\n";
825 osync_trace(TRACE_INTERNAL
, "Failed editing event: (etag: %s). HTTP code: %d, %s, %s\n",
827 gcal_status_httpcode(gdata
->handle
),
828 gcal_status_msg(gdata
->handle
),
829 gcal_access_buffer(gdata
->handle
));
833 event
= gcal_event_new(updated_event
);
835 msg
= "Failed recovering updated fields!\n";
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
),
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";
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
));
865 msg
= "Failed setting url for event delete\n";
869 result
= gcal_erase_event(gdata
->handle
, event
);
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
));
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";
889 msg
= "Unknown change type";
893 if( event
&& gcal_event_get_updated(event
) ) {
894 // update the timestamp
895 if( gdata
->timestamp
) {
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
));
903 gdata
->timestamp
= strdup(gcal_event_get_updated(event
));
907 if (!gdata
->timestamp
) {
908 msg
= "Failed copying contact timestamp!\n";
913 osync_context_report_success(ctx
);
914 osync_trace(TRACE_EXIT
, "%s", __func__
);
919 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "%s", msg
);
920 osync_trace(TRACE_EXIT
, "%s:%sResult code: %d", __func__
, msg
, result
);
926 gcal_event_delete(event
);
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
;
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";
953 osync_data_get_data(odata
, &osync_xml
, &size
);
955 msg
= "Failed getting xml from xmlobj!\n";
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";
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
);
972 msg
= "Failed adding new contact!\n";
973 result
= gcal_status_httpcode(gdata
->handle
);
977 if (!(contact
= gcal_contact_new(updated_contact
))) {
978 msg
= "Failed recovering updated fields!\n";
983 case OSYNC_CHANGE_TYPE_MODIFIED
:
984 result
= gcal_update_xmlentry(gdata
->handle
,
985 raw_xml
, &updated_contact
, NULL
, NULL
);
987 msg
= "Failed editing contact!\n";
991 if (!(contact
= gcal_contact_new(updated_contact
))) {
992 msg
= "Failed recovering updated fields!\n";
997 case OSYNC_CHANGE_TYPE_DELETED
:
998 result
= gcal_erase_xmlentry(gdata
->handle
, raw_xml
);
1000 msg
= "Failed deleting contact!\n";
1006 osync_context_report_error(ctx
, OSYNC_ERROR_NOT_SUPPORTED
,
1007 "Unknown change type");
1012 if (updated_contact
)
1013 free(updated_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";
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
);
1033 osync_context_report_success(ctx
);
1034 osync_trace(TRACE_EXIT
, "%s", __func__
);
1039 if (updated_contact
)
1040 free(updated_contact
);
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
,
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 /////////////////////////////////////////////////////////////////////////////
1078 static void *gc_initialize(OSyncPlugin
*plugin
,
1079 OSyncPluginInfo
*info
,
1082 osync_trace(TRACE_ENTRY
, "%s(%p, %p, %p)", __func__
, plugin
, info
, error
);
1083 struct gc_plgdata
*plgdata
;
1084 OSyncPluginConfig
*config
;
1085 OSyncList
*resources
;
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.");
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";
1109 plgdata
->xslt_path
= strdup(osync_plugin_get_default_configdir());
1110 if( !plgdata
->xslt_path
)
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
)
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
)
1135 osync_trace(TRACE_INTERNAL
,
1136 "\tcreated contact obj!\n");
1137 gcal_set_store_xml(plgdata
->cont
.handle
, 1);
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
) ) {
1150 osync_plugin_authentication_get_username(optauth
);
1154 plgdata
->username
= strdup(user
);
1155 if( !plgdata
->username
)
1163 if( osync_plugin_authentication_option_is_supported(optauth
,
1164 OSYNC_PLUGIN_AUTHENTICATION_PASSWORD
) ) {
1166 osync_plugin_authentication_get_password(optauth
);
1170 plgdata
->password
= strdup(pass
);
1171 if( !plgdata
->password
)
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");
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!");
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
1218 sink
= osync_plugin_info_find_objtype(info
, "contact");
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!");
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
)
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
)
1263 osync_trace(TRACE_INTERNAL
, "\tsucceed creating xslt_gcont!\n");
1266 osync_trace(TRACE_EXIT
, "%s", __func__
);
1273 osync_trace(TRACE_EXIT_ERROR
, "%s: %s", __func__
, osync_error_print(error
));
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__
);
1297 static void gc_finalize(void *data
)
1299 osync_trace(TRACE_ENTRY
, "%s(%p)", __func__
, data
);
1300 struct gc_plgdata
*plgdata
= data
;
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
);
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
) )
1325 osync_plugin_unref(plugin
);
1327 osync_trace(TRACE_EXIT
, "%s", __func__
);
1331 osync_trace(TRACE_EXIT_ERROR
, "Unable to register: %s",
1332 osync_error_print(error
));
1336 int get_version(void)