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 xmlFree(plgdata
->url
);
59 if (plgdata
->username
)
60 xmlFree(plgdata
->username
);
61 if (plgdata
->password
)
62 xmlFree(plgdata
->password
);
63 if (plgdata
->hashtable
)
64 osync_hashtable_free(plgdata
->hashtable
);
66 osync_objtype_sink_unref(plgdata
->sink
);
67 if (plgdata
->objformat
)
68 osync_objformat_unref(plgdata
->objformat
);
72 /** Run gchelper and return the file descriptors for its stdin/stdout
75 osync_bool
run_helper(struct gc_plgdata
*plgdata
, const char *operation
,
76 const char *arg
, int *in
, int *out
, pid_t
*ppid
,
80 osync_bool result
= FALSE
;
81 /* TODO: add calls to libgcal */
86 char *gc_get_cfgvalue(xmlNode
*cfg
, const char *name
)
89 for (c
= cfg
->xmlChildrenNode
; c
; c
= c
->next
) {
90 if (!xmlStrcmp(c
->name
, (const xmlChar
*)name
))
91 return (char*)xmlNodeGetContent(c
);
96 osync_bool
gc_parse_config(struct gc_plgdata
*plgdata
, const char *cfg
, OSyncError
**error
)
100 osync_bool ret
= FALSE
;
102 doc
= xmlParseMemory(cfg
, strlen(cfg
) + 1);
104 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "Couldn't parse configuration");
108 node
= xmlDocGetRootElement(doc
);
109 if (!node
|| xmlStrcmp(node
->name
, (const xmlChar
*)"config")) {
110 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "Invalid configuration");
114 /*TODO: Make this more user-friendly: allow the URL to be omitted
115 * by the user and build it automatically from the username
117 plgdata
->url
= gc_get_cfgvalue(node
, "url");
118 plgdata
->username
= gc_get_cfgvalue(node
, "username");
119 /*FIXME: We need an opensync API for getting info from the user,
122 plgdata
->password
= gc_get_cfgvalue(node
, "password");
124 if (!plgdata
->url
|| !plgdata
->username
|| !plgdata
->password
) {
125 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "Invalid configuration");
137 xmlFree(plgdata
->url
);
138 xmlFree(plgdata
->username
);
139 xmlFree(plgdata
->password
);
143 static void gc_connect(void *data
, OSyncPluginInfo
*info
, OSyncContext
*ctx
)
145 osync_trace(TRACE_ENTRY
, "%s(%p, %p, %p)", __func__
, data
, info
, ctx
);
147 struct gc_plgdata
*plgdata
= data
;
148 OSyncObjTypeSink
*sink
= osync_plugin_info_get_sink(info
);
149 OSyncError
*error
= NULL
;
151 char *tablepath
= g_strdup_printf("%s/hashtable.db", osync_plugin_info_get_configdir(info
));
153 plgdata
->hashtable
= osync_hashtable_new(tablepath
, osync_objtype_sink_get_name(sink
), &error
);
156 if (!plgdata
->hashtable
) {
157 osync_context_report_osyncerror(ctx
, &error
);
161 osync_context_report_success(ctx
);
163 osync_trace(TRACE_EXIT
, "%s", __func__
);
166 static void gc_get_changes(void *data
, OSyncPluginInfo
*info
, OSyncContext
*ctx
)
168 osync_trace(TRACE_ENTRY
, "%s(%p, %p, %p)", __func__
, data
, info
, ctx
);
170 struct gc_plgdata
*plgdata
= data
;
171 OSyncObjTypeSink
*sink
= osync_plugin_info_get_sink(info
);
172 OSyncError
*error
= NULL
;
180 /* Flush internal reports of hashtable to determin deleted entries. */
181 osync_hashtable_reset_reports(plgdata
->hashtable
);
183 if (osync_objtype_sink_get_slowsync(sink
)) {
184 if (!osync_hashtable_slowsync(plgdata
->hashtable
, &error
)) {
185 osync_context_report_osyncerror(ctx
, &error
);
190 if (!run_helper(plgdata
, "get_all", NULL
,
191 NULL
, &output
, &pid
, &error
)) {
192 osync_context_report_osyncerror(ctx
, &error
);
196 out
= fdopen(output
, "r");
198 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Couldn't open helper output");
203 while (fgets(sizeline
, sizeof(sizeline
), out
)) {
204 int size
, uidsize
, hashsize
;
205 char *xmldata
, *uid
, *hash
;
207 if (sscanf(sizeline
, "%d %d %d", &size
, &uidsize
, &hashsize
) < 3) {
208 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Invalid size line from helper");
212 xmldata
= malloc(size
);
214 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "No memory");
218 uid
= malloc(uidsize
+ 1);
220 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "No memory");
224 hash
= malloc(hashsize
+ 1);
226 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "No memory");
230 if (fread(xmldata
, size
, 1, out
) < 1) {
231 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Error reading xml data from helper");
235 if (fread(uid
, uidsize
, 1, out
) < 1) {
236 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Error reading xml data from helper");
241 if (fread(hash
, hashsize
, 1, out
) < 1) {
242 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Error reading xml data from helper");
245 hash
[hashsize
] = '\0';
247 OSyncXMLFormat
*doc
= osync_xmlformat_parse(xmldata
, size
, &error
);
249 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Invalid XML data from helper");
250 osync_error_unref(&error
);
253 /* osync_merge_merge() seems to like its input sorted... */
254 osync_xmlformat_sort(doc
);
256 OSyncData
*odata
= osync_data_new((char *)doc
, osync_xmlformat_size(), plgdata
->objformat
, &error
);
258 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "No memory");
259 osync_error_unref(&error
);
260 /* osync_data_new() does not increase the reference count of
261 its 'data' member, but osync_data_unref() will decrease it,
262 so this is the only case where 'doc' has to be unreferenced
264 osync_xmlformat_unref(doc
);
268 OSyncChange
*chg
= osync_change_new(&error
);
270 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "No memory");
271 osync_error_unref(&error
);
275 osync_change_set_uid(chg
, uid
);
276 osync_change_set_data(chg
, odata
);
277 osync_data_unref(odata
);
278 osync_change_set_objtype(chg
, osync_objtype_sink_get_name(sink
));
279 osync_change_set_hash(chg
, hash
);
281 OSyncChangeType type
= osync_hashtable_get_changetype(plgdata
->hashtable
,
283 osync_change_set_changetype(chg
, type
);
284 osync_hashtable_report(plgdata
->hashtable
, uid
);
286 if (osync_change_get_changetype(chg
) != OSYNC_CHANGE_TYPE_UNMODIFIED
) {
287 osync_context_report_change(ctx
, chg
);
288 osync_hashtable_update_hash(plgdata
->hashtable
, osync_change_get_changetype(chg
), uid
, hash
);
291 osync_change_unref(chg
);
299 /* error handling in the loop */
301 osync_data_unref(odata
);
311 char **uids
= osync_hashtable_get_deleted(plgdata
->hashtable
);
313 for (i
= 0; uids
[i
]; i
++) {
314 OSyncChange
*change
= osync_change_new(&error
);
316 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "ERROR is: %s", osync_error_print(&error
));
319 OSyncData
*data
= osync_data_new(NULL
, 0, plgdata
->objformat
, &error
);
321 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "ERROR is: %s", osync_error_print(&error
));
324 osync_data_set_objtype(data
, "event");
326 osync_change_set_data(change
, data
);
327 osync_change_set_changetype(change
, OSYNC_CHANGE_TYPE_DELETED
);
328 osync_change_set_uid(change
, uids
[i
]);
329 osync_context_report_change(ctx
, change
);
330 osync_hashtable_update_hash(plgdata
->hashtable
, OSYNC_CHANGE_TYPE_DELETED
, uids
[i
], NULL
);
331 osync_change_unref(change
);
332 osync_data_unref(data
);
338 waitpid(pid
, &status
, 0);
340 if (!WIFEXITED(status
) || WEXITSTATUS(status
) != 0) {
341 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Helper exited abnormally");
345 osync_context_report_success(ctx
);
347 osync_trace(TRACE_EXIT
, "%s", __func__
);
355 waitpid(pid
, NULL
, 0);
357 osync_trace(TRACE_EXIT
, "%s", __func__
);
361 static void gc_commit_change(void *data
, OSyncPluginInfo
*info
,
362 OSyncContext
*ctx
, OSyncChange
*change
)
364 osync_trace(TRACE_ENTRY
, "%s(%p, %p, %p)", __func__
, data
, info
, ctx
, change
);
366 OSyncObjTypeSink
*sink
= osync_plugin_info_get_sink(info
);
367 struct gc_plgdata
*plgdata
= data
;
371 OSyncError
*error
= NULL
;
379 hash
= osync_hashtable_get_hash(plgdata
->hashtable
, osync_change_get_uid(change
));
381 switch (osync_change_get_changetype(change
)) {
382 case OSYNC_CHANGE_TYPE_ADDED
:
386 case OSYNC_CHANGE_TYPE_MODIFIED
:
390 case OSYNC_CHANGE_TYPE_DELETED
:
395 osync_context_report_error(ctx
, OSYNC_ERROR_NOT_SUPPORTED
, "Unknown change type");
400 if (!run_helper(plgdata
, cmd
, arg
,
401 &input
, &output
, &pid
, &error
)) {
402 osync_context_report_osyncerror(ctx
, &error
);
408 out
= fdopen(output
, "r");
410 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Couldn't open helper output");
415 switch (osync_change_get_changetype(change
)) {
416 case OSYNC_CHANGE_TYPE_ADDED
:
417 case OSYNC_CHANGE_TYPE_MODIFIED
:
419 OSyncXMLFormat
*doc
= (OSyncXMLFormat
*)osync_data_get_data_ptr(osync_change_get_data(change
));
423 osync_xmlformat_assemble(doc
, &buffer
, &size
);
424 osync_trace(TRACE_INTERNAL
, "input to helper:\n%s", buffer
);
425 if (write(input
, buffer
, size
) < size
) {
426 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Couldn't write data to helper");
435 int xmlsize
, uidsize
, hashsize
;
436 char *xmldata
, *uid
, *hash
;
438 if (!fgets(sizeline
, sizeof(sizeline
), out
)) {
439 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Couldn't read from helper");
443 if (sscanf(sizeline
, "%d %d %d", &xmlsize
, &uidsize
, &hashsize
) < 3) {
444 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Invalid size line from helper");
448 xmldata
= malloc(xmlsize
);
450 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "No memory");
454 uid
= malloc(uidsize
+ 1);
456 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "No memory");
460 hash
= malloc(hashsize
+ 1);
462 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "No memory");
466 if (fread(xmldata
, xmlsize
, 1, out
) < 1) {
467 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Error reading xml data from helper");
472 if (fread(uid
, uidsize
, 1, out
) < 1) {
473 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Error reading xml data from helper");
478 if (fread(hash
, hashsize
, 1, out
) < 1) {
479 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Error reading xml data from helper");
482 hash
[hashsize
] = '\0';
484 /* Done writing. Update UID and hash */
485 osync_change_set_uid(change
, uid
);
486 osync_change_set_hash(change
, hash
);
506 case OSYNC_CHANGE_TYPE_DELETED
:
510 g_assert_not_reached();
513 osync_hashtable_update_hash(plgdata
->hashtable
,
514 osync_change_get_changetype(change
),
515 osync_change_get_uid(change
),
516 osync_change_get_hash(change
));
520 waitpid(pid
, &status
, 0);
522 if (!WIFEXITED(status
) || WEXITSTATUS(status
) != 0) {
523 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
, "Helper exited abnormally");
527 osync_context_report_success(ctx
);
529 osync_trace(TRACE_EXIT
, "%s", __func__
);
537 waitpid(pid
, NULL
, 0);
539 osync_trace(TRACE_EXIT_ERROR
, "%s: %s", __func__
, osync_error_print(&error
));
543 static void gc_disconnect(void *data
, OSyncPluginInfo
*info
, OSyncContext
*ctx
)
545 osync_trace(TRACE_ENTRY
, "%s(%p, %p, %p)", __func__
, data
, info
, ctx
);
546 struct gc_plgdata
*plgdata
= data
;
548 osync_hashtable_free(plgdata
->hashtable
);
549 plgdata
->hashtable
= NULL
;
551 osync_context_report_success(ctx
);
552 osync_trace(TRACE_EXIT
, "%s", __func__
);
555 static void gc_finalize(void *data
)
557 osync_trace(TRACE_ENTRY
, "%s(%p)", __func__
, data
);
558 struct gc_plgdata
*plgdata
= data
;
561 osync_trace(TRACE_EXIT
, "%s", __func__
);
564 static void *gc_initialize(OSyncPlugin
*plugin
,
565 OSyncPluginInfo
*info
,
568 osync_trace(TRACE_ENTRY
, "%s(%p, %p, %p)", __func__
, plugin
, info
, error
);
569 struct gc_plgdata
*plgdata
;
572 plgdata
= osync_try_malloc0(sizeof(struct gc_plgdata
), error
);
576 cfg
= osync_plugin_info_get_config(info
);
578 osync_error_set(error
, OSYNC_ERROR_GENERIC
,
579 "Unable to get config data.");
583 if (!gc_parse_config(plgdata
, cfg
, error
))
586 OSyncFormatEnv
*formatenv
= osync_plugin_info_get_format_env(info
);
587 plgdata
->objformat
= osync_format_env_find_objformat(formatenv
, "xmlformat-event");
588 if (!plgdata
->objformat
)
590 osync_objformat_ref(plgdata
->objformat
);
592 plgdata
->sink
= osync_objtype_sink_new("event", error
);
596 osync_objtype_sink_add_objformat(plgdata
->sink
, "xmlformat-event");
598 OSyncObjTypeSinkFunctions functions
;
599 memset(&functions
, 0, sizeof(functions
));
600 functions
.connect
= gc_connect
;
601 functions
.disconnect
= gc_disconnect
;
602 functions
.get_changes
= gc_get_changes
;
603 functions
.commit
= gc_commit_change
;
605 osync_objtype_sink_set_functions(plgdata
->sink
, functions
, plgdata
);
606 osync_plugin_info_add_objtype(info
, plgdata
->sink
);
608 osync_trace(TRACE_EXIT
, "%s", __func__
);
615 osync_trace(TRACE_EXIT_ERROR
, "%s: %s", __func__
, osync_error_print(error
));
619 static osync_bool
gc_discover(void *data
, OSyncPluginInfo
*info
, OSyncError
**error
)
621 osync_trace(TRACE_ENTRY
, "%s(%p, %p, %p)", __func__
, data
, info
, error
);
623 struct gc_plgdata
*plgdata
= data
;
625 osync_objtype_sink_set_available(plgdata
->sink
, TRUE
);
627 OSyncVersion
*version
= osync_version_new(error
);
628 osync_version_set_plugin(version
, "google-calendar");
629 osync_plugin_info_set_version(info
, version
);
630 osync_version_unref(version
);
632 osync_trace(TRACE_EXIT
, "%s", __func__
);
636 osync_bool
get_sync_info(OSyncPluginEnv
*env
, OSyncError
**error
)
638 osync_trace(TRACE_ENTRY
, "%s(%p, %p)", __func__
, env
, error
);
639 OSyncPlugin
*plugin
= osync_plugin_new(error
);
643 osync_plugin_set_name(plugin
, "google-calendar");
644 osync_plugin_set_longname(plugin
, "Google Calendar");
645 osync_plugin_set_description(plugin
, "Google Calendar plugin");
646 osync_plugin_set_config_type(plugin
, OSYNC_PLUGIN_NEEDS_CONFIGURATION
);
648 osync_plugin_set_initialize(plugin
, gc_initialize
);
649 osync_plugin_set_finalize(plugin
, gc_finalize
);
650 osync_plugin_set_discover(plugin
, gc_discover
);
652 osync_plugin_env_register_plugin(env
, plugin
);
653 osync_plugin_unref(plugin
);
655 osync_trace(TRACE_EXIT
, "%s", __func__
);
659 osync_trace(TRACE_EXIT_ERROR
, "Unable to register: %s", osync_error_print(error
));
660 osync_error_unref(error
);
664 int get_version(void)