1 /** Google Calendar plugin
3 * Copyright (c) 2006 Eduardo Pereira Habkost <ehabkost@raisama.net>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 #include <opensync/opensync.h>
23 #include <opensync/opensync-plugin.h>
24 #include <opensync/opensync-helper.h>
25 #include <opensync/opensync-merger.h>
26 #include <opensync/opensync-format.h>
27 #include <opensync/opensync-data.h>
28 #include <opensync/opensync-version.h>
32 #include <libxml/tree.h>
37 #include <sys/types.h>
40 #include <gcal_status.h>
41 #include <gcalendar.h>
49 struct gcal_event_array all_events
;
50 OSyncHashTable
*hashtable
;
51 OSyncObjTypeSink
*sink
;
52 OSyncObjFormat
*objformat
;
55 static void free_plg(struct gc_plgdata
*plgdata
)
58 gcal_delete(plgdata
->gcal
);
60 xmlFree(plgdata
->url
);
61 if (plgdata
->username
)
62 xmlFree(plgdata
->username
);
63 if (plgdata
->password
)
64 xmlFree(plgdata
->password
);
65 if (plgdata
->hashtable
)
66 osync_hashtable_free(plgdata
->hashtable
);
68 osync_objtype_sink_unref(plgdata
->sink
);
69 if (plgdata
->objformat
)
70 osync_objformat_unref(plgdata
->objformat
);
74 /** Run gchelper and return the file descriptors for its stdin/stdout
77 osync_bool
run_helper(struct gc_plgdata
*plgdata
, const char *operation
,
78 const char *arg
, int *in
, int *out
, pid_t
*ppid
,
82 osync_bool result
= FALSE
;
83 /* TODO: add calls to libgcal */
88 char *gc_get_cfgvalue(xmlNode
*cfg
, const char *name
)
91 for (c
= cfg
->xmlChildrenNode
; c
; c
= c
->next
) {
92 if (!xmlStrcmp(c
->name
, (const xmlChar
*)name
))
93 return (char*)xmlNodeGetContent(c
);
98 osync_bool
gc_parse_config(struct gc_plgdata
*plgdata
, const char *cfg
, OSyncError
**error
)
102 osync_bool ret
= FALSE
;
104 doc
= xmlParseMemory(cfg
, strlen(cfg
) + 1);
106 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "Couldn't parse configuration");
110 node
= xmlDocGetRootElement(doc
);
111 if (!node
|| xmlStrcmp(node
->name
, (const xmlChar
*)"config")) {
112 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "Invalid configuration");
116 /*TODO: Make this more user-friendly: allow the URL to be omitted
117 * by the user and build it automatically from the username
119 plgdata
->url
= gc_get_cfgvalue(node
, "url");
120 plgdata
->username
= gc_get_cfgvalue(node
, "username");
121 /*FIXME: We need an opensync API for getting info from the user,
124 plgdata
->password
= gc_get_cfgvalue(node
, "password");
126 if (!plgdata
->url
|| !plgdata
->username
|| !plgdata
->password
) {
127 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "Invalid configuration");
139 xmlFree(plgdata
->url
);
140 xmlFree(plgdata
->username
);
141 xmlFree(plgdata
->password
);
145 static void gc_connect(void *data
, OSyncPluginInfo
*info
, OSyncContext
*ctx
)
147 osync_trace(TRACE_ENTRY
, "%s(%p, %p, %p)", __func__
, data
, info
, ctx
);
150 struct gc_plgdata
*plgdata
= data
;
151 OSyncObjTypeSink
*sink
= osync_plugin_info_get_sink(info
);
152 OSyncError
*error
= NULL
;
154 char *tablepath
= g_strdup_printf("%s/hashtable.db", osync_plugin_info_get_configdir(info
));
156 plgdata
->hashtable
= osync_hashtable_new(tablepath
, osync_objtype_sink_get_name(sink
), &error
);
159 plgdata
->gcal
= gcal_new(GCALENDAR
);
160 result
= gcal_get_authentication(plgdata
->gcal
, plgdata
->username
,
162 if ((!plgdata
->gcal
) || (result
== -1)) {
163 osync_context_report_osyncerror(ctx
, &error
);
167 if (!plgdata
->hashtable
) {
168 osync_context_report_osyncerror(ctx
, &error
);
172 osync_context_report_success(ctx
);
174 osync_trace(TRACE_EXIT
, "%s", __func__
);
177 static void gc_get_changes(void *data
, OSyncPluginInfo
*info
, OSyncContext
*ctx
)
179 osync_trace(TRACE_ENTRY
, "%s(%p, %p, %p)", __func__
, data
, info
, ctx
);
181 struct gc_plgdata
*plgdata
= data
;
182 OSyncObjTypeSink
*sink
= osync_plugin_info_get_sink(info
);
183 OSyncError
*error
= NULL
;
191 /* Flush internal reports of hashtable to determin deleted entries. */
192 osync_hashtable_reset_reports(plgdata
->hashtable
);
194 if (osync_objtype_sink_get_slowsync(sink
)) {
195 if (!osync_hashtable_slowsync(plgdata
->hashtable
, &error
)) {
196 osync_context_report_osyncerror(ctx
, &error
);
201 if (!run_helper(plgdata
, "get_all", NULL
,
202 NULL
, &output
, &pid
, &error
)) {
203 osync_context_report_osyncerror(ctx
, &error
);
207 out
= fdopen(output
, "r");
209 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Couldn't open helper output");
214 while (fgets(sizeline
, sizeof(sizeline
), out
)) {
215 int size
, uidsize
, hashsize
;
216 char *xmldata
, *uid
, *hash
;
218 if (sscanf(sizeline
, "%d %d %d", &size
, &uidsize
, &hashsize
) < 3) {
219 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Invalid size line from helper");
223 xmldata
= malloc(size
);
225 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "No memory");
229 uid
= malloc(uidsize
+ 1);
231 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "No memory");
235 hash
= malloc(hashsize
+ 1);
237 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "No memory");
241 if (fread(xmldata
, size
, 1, out
) < 1) {
242 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Error reading xml data from helper");
246 if (fread(uid
, uidsize
, 1, out
) < 1) {
247 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Error reading xml data from helper");
252 if (fread(hash
, hashsize
, 1, out
) < 1) {
253 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Error reading xml data from helper");
256 hash
[hashsize
] = '\0';
258 OSyncXMLFormat
*doc
= osync_xmlformat_parse(xmldata
, size
, &error
);
260 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Invalid XML data from helper");
261 osync_error_unref(&error
);
264 /* osync_merge_merge() seems to like its input sorted... */
265 osync_xmlformat_sort(doc
);
267 OSyncData
*odata
= osync_data_new((char *)doc
, osync_xmlformat_size(), plgdata
->objformat
, &error
);
269 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "No memory");
270 osync_error_unref(&error
);
271 /* osync_data_new() does not increase the reference count of
272 its 'data' member, but osync_data_unref() will decrease it,
273 so this is the only case where 'doc' has to be unreferenced
275 osync_xmlformat_unref(doc
);
279 OSyncChange
*chg
= osync_change_new(&error
);
281 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "No memory");
282 osync_error_unref(&error
);
286 osync_change_set_uid(chg
, uid
);
287 osync_change_set_data(chg
, odata
);
288 osync_data_unref(odata
);
289 osync_change_set_objtype(chg
, osync_objtype_sink_get_name(sink
));
290 osync_change_set_hash(chg
, hash
);
292 OSyncChangeType type
= osync_hashtable_get_changetype(plgdata
->hashtable
,
294 osync_change_set_changetype(chg
, type
);
295 osync_hashtable_report(plgdata
->hashtable
, uid
);
297 if (osync_change_get_changetype(chg
) != OSYNC_CHANGE_TYPE_UNMODIFIED
) {
298 osync_context_report_change(ctx
, chg
);
299 osync_hashtable_update_hash(plgdata
->hashtable
, osync_change_get_changetype(chg
), uid
, hash
);
302 osync_change_unref(chg
);
310 /* error handling in the loop */
312 osync_data_unref(odata
);
322 char **uids
= osync_hashtable_get_deleted(plgdata
->hashtable
);
324 for (i
= 0; uids
[i
]; i
++) {
325 OSyncChange
*change
= osync_change_new(&error
);
327 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "ERROR is: %s", osync_error_print(&error
));
330 OSyncData
*data
= osync_data_new(NULL
, 0, plgdata
->objformat
, &error
);
332 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "ERROR is: %s", osync_error_print(&error
));
335 osync_data_set_objtype(data
, "event");
337 osync_change_set_data(change
, data
);
338 osync_change_set_changetype(change
, OSYNC_CHANGE_TYPE_DELETED
);
339 osync_change_set_uid(change
, uids
[i
]);
340 osync_context_report_change(ctx
, change
);
341 osync_hashtable_update_hash(plgdata
->hashtable
, OSYNC_CHANGE_TYPE_DELETED
, uids
[i
], NULL
);
342 osync_change_unref(change
);
343 osync_data_unref(data
);
349 waitpid(pid
, &status
, 0);
351 if (!WIFEXITED(status
) || WEXITSTATUS(status
) != 0) {
352 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Helper exited abnormally");
356 osync_context_report_success(ctx
);
358 osync_trace(TRACE_EXIT
, "%s", __func__
);
366 waitpid(pid
, NULL
, 0);
368 osync_trace(TRACE_EXIT
, "%s", __func__
);
372 static void gc_commit_change(void *data
, OSyncPluginInfo
*info
,
373 OSyncContext
*ctx
, OSyncChange
*change
)
375 osync_trace(TRACE_ENTRY
, "%s(%p, %p, %p)", __func__
, data
, info
, ctx
, change
);
377 OSyncObjTypeSink
*sink
= osync_plugin_info_get_sink(info
);
378 struct gc_plgdata
*plgdata
= data
;
382 OSyncError
*error
= NULL
;
390 hash
= osync_hashtable_get_hash(plgdata
->hashtable
, osync_change_get_uid(change
));
392 switch (osync_change_get_changetype(change
)) {
393 case OSYNC_CHANGE_TYPE_ADDED
:
397 case OSYNC_CHANGE_TYPE_MODIFIED
:
401 case OSYNC_CHANGE_TYPE_DELETED
:
406 osync_context_report_error(ctx
, OSYNC_ERROR_NOT_SUPPORTED
, "Unknown change type");
411 if (!run_helper(plgdata
, cmd
, arg
,
412 &input
, &output
, &pid
, &error
)) {
413 osync_context_report_osyncerror(ctx
, &error
);
419 out
= fdopen(output
, "r");
421 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Couldn't open helper output");
426 switch (osync_change_get_changetype(change
)) {
427 case OSYNC_CHANGE_TYPE_ADDED
:
428 case OSYNC_CHANGE_TYPE_MODIFIED
:
430 OSyncXMLFormat
*doc
= (OSyncXMLFormat
*)osync_data_get_data_ptr(osync_change_get_data(change
));
434 osync_xmlformat_assemble(doc
, &buffer
, &size
);
435 osync_trace(TRACE_INTERNAL
, "input to helper:\n%s", buffer
);
436 if (write(input
, buffer
, size
) < size
) {
437 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Couldn't write data to helper");
446 int xmlsize
, uidsize
, hashsize
;
447 char *xmldata
, *uid
, *hash
;
449 if (!fgets(sizeline
, sizeof(sizeline
), out
)) {
450 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Couldn't read from helper");
454 if (sscanf(sizeline
, "%d %d %d", &xmlsize
, &uidsize
, &hashsize
) < 3) {
455 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Invalid size line from helper");
459 xmldata
= malloc(xmlsize
);
461 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "No memory");
465 uid
= malloc(uidsize
+ 1);
467 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "No memory");
471 hash
= malloc(hashsize
+ 1);
473 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "No memory");
477 if (fread(xmldata
, xmlsize
, 1, out
) < 1) {
478 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Error reading xml data from helper");
483 if (fread(uid
, uidsize
, 1, out
) < 1) {
484 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Error reading xml data from helper");
489 if (fread(hash
, hashsize
, 1, out
) < 1) {
490 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Error reading xml data from helper");
493 hash
[hashsize
] = '\0';
495 /* Done writing. Update UID and hash */
496 osync_change_set_uid(change
, uid
);
497 osync_change_set_hash(change
, hash
);
517 case OSYNC_CHANGE_TYPE_DELETED
:
521 g_assert_not_reached();
524 osync_hashtable_update_hash(plgdata
->hashtable
,
525 osync_change_get_changetype(change
),
526 osync_change_get_uid(change
),
527 osync_change_get_hash(change
));
531 waitpid(pid
, &status
, 0);
533 if (!WIFEXITED(status
) || WEXITSTATUS(status
) != 0) {
534 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Helper exited abnormally");
538 osync_context_report_success(ctx
);
540 osync_trace(TRACE_EXIT
, "%s", __func__
);
548 waitpid(pid
, NULL
, 0);
550 osync_trace(TRACE_EXIT_ERROR
, "%s: %s", __func__
, osync_error_print(&error
));
554 static void gc_disconnect(void *data
, OSyncPluginInfo
*info
, OSyncContext
*ctx
)
556 osync_trace(TRACE_ENTRY
, "%s(%p, %p, %p)", __func__
, data
, info
, ctx
);
557 struct gc_plgdata
*plgdata
= data
;
559 osync_hashtable_free(plgdata
->hashtable
);
560 plgdata
->hashtable
= NULL
;
562 osync_context_report_success(ctx
);
563 osync_trace(TRACE_EXIT
, "%s", __func__
);
566 static void gc_finalize(void *data
)
568 osync_trace(TRACE_ENTRY
, "%s(%p)", __func__
, data
);
569 struct gc_plgdata
*plgdata
= data
;
572 osync_trace(TRACE_EXIT
, "%s", __func__
);
575 static void *gc_initialize(OSyncPlugin
*plugin
,
576 OSyncPluginInfo
*info
,
579 osync_trace(TRACE_ENTRY
, "%s(%p, %p, %p)", __func__
, plugin
, info
, error
);
580 struct gc_plgdata
*plgdata
;
583 plgdata
= osync_try_malloc0(sizeof(struct gc_plgdata
), error
);
587 cfg
= osync_plugin_info_get_config(info
);
589 osync_error_set(error
, OSYNC_ERROR_GENERIC
,
590 "Unable to get config data.");
594 if (!gc_parse_config(plgdata
, cfg
, error
))
597 OSyncFormatEnv
*formatenv
= osync_plugin_info_get_format_env(info
);
598 plgdata
->objformat
= osync_format_env_find_objformat(formatenv
, "xmlformat-event");
599 if (!plgdata
->objformat
)
601 osync_objformat_ref(plgdata
->objformat
);
603 plgdata
->sink
= osync_objtype_sink_new("event", error
);
607 osync_objtype_sink_add_objformat(plgdata
->sink
, "xmlformat-event");
609 OSyncObjTypeSinkFunctions functions
;
610 memset(&functions
, 0, sizeof(functions
));
611 functions
.connect
= gc_connect
;
612 functions
.disconnect
= gc_disconnect
;
613 functions
.get_changes
= gc_get_changes
;
614 functions
.commit
= gc_commit_change
;
616 osync_objtype_sink_set_functions(plgdata
->sink
, functions
, plgdata
);
617 osync_plugin_info_add_objtype(info
, plgdata
->sink
);
619 osync_trace(TRACE_EXIT
, "%s", __func__
);
626 osync_trace(TRACE_EXIT_ERROR
, "%s: %s", __func__
, osync_error_print(error
));
630 static osync_bool
gc_discover(void *data
, OSyncPluginInfo
*info
, OSyncError
**error
)
632 osync_trace(TRACE_ENTRY
, "%s(%p, %p, %p)", __func__
, data
, info
, error
);
634 struct gc_plgdata
*plgdata
= data
;
636 osync_objtype_sink_set_available(plgdata
->sink
, TRUE
);
638 OSyncVersion
*version
= osync_version_new(error
);
639 osync_version_set_plugin(version
, "google-calendar");
640 osync_plugin_info_set_version(info
, version
);
641 osync_version_unref(version
);
643 osync_trace(TRACE_EXIT
, "%s", __func__
);
647 osync_bool
get_sync_info(OSyncPluginEnv
*env
, OSyncError
**error
)
649 osync_trace(TRACE_ENTRY
, "%s(%p, %p)", __func__
, env
, error
);
650 OSyncPlugin
*plugin
= osync_plugin_new(error
);
654 osync_plugin_set_name(plugin
, "google-calendar");
655 osync_plugin_set_longname(plugin
, "Google Calendar");
656 osync_plugin_set_description(plugin
, "Google Calendar plugin");
657 osync_plugin_set_config_type(plugin
, OSYNC_PLUGIN_NEEDS_CONFIGURATION
);
659 osync_plugin_set_initialize(plugin
, gc_initialize
);
660 osync_plugin_set_finalize(plugin
, gc_finalize
);
661 osync_plugin_set_discover(plugin
, gc_discover
);
663 osync_plugin_env_register_plugin(env
, plugin
);
664 osync_plugin_unref(plugin
);
666 osync_trace(TRACE_EXIT
, "%s", __func__
);
670 osync_trace(TRACE_EXIT_ERROR
, "Unable to register: %s", osync_error_print(error
));
671 osync_error_unref(error
);
675 int get_version(void)