Moving free of gcal type to other function (since 'free_plg' is the
[gdataplugin.git] / src / gcalendar.c
blobc8b26343a1a581ab78dd9267e4b8fe400bc69759
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.
9 *
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
18 * 02110-1301 USA
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>
30 #include <glib.h>
32 #include <libxml/tree.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <sys/types.h>
38 #include <signal.h>
39 #include <sys/wait.h>
40 #include <gcal_status.h>
41 #include <gcalendar.h>
43 struct gc_plgdata
45 char *url;
46 char *username;
47 char *password;
48 gcal_t gcal;
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)
57 if (plgdata->gcal)
58 gcal_delete(plgdata->gcal);
59 if (plgdata->url)
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);
67 if (plgdata->sink)
68 osync_objtype_sink_unref(plgdata->sink);
69 if (plgdata->objformat)
70 osync_objformat_unref(plgdata->objformat);
71 g_free(plgdata);
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,
79 OSyncError **error)
82 osync_bool result = FALSE;
83 /* TODO: add calls to libgcal */
84 exit:
85 return result;
88 char *gc_get_cfgvalue(xmlNode *cfg, const char *name)
90 xmlNode *c;
91 for (c = cfg->xmlChildrenNode; c; c = c->next) {
92 if (!xmlStrcmp(c->name, (const xmlChar*)name))
93 return (char*)xmlNodeGetContent(c);
95 return NULL;
98 osync_bool gc_parse_config(struct gc_plgdata *plgdata, const char *cfg, OSyncError **error)
100 xmlNode *node;
101 xmlDoc *doc;
102 osync_bool ret = FALSE;
104 doc = xmlParseMemory(cfg, strlen(cfg) + 1);
105 if (!doc) {
106 osync_error_set(error, OSYNC_ERROR_GENERIC, "Couldn't parse configuration");
107 goto out;
110 node = xmlDocGetRootElement(doc);
111 if (!node || xmlStrcmp(node->name, (const xmlChar*)"config")) {
112 osync_error_set(error, OSYNC_ERROR_GENERIC, "Invalid configuration");
113 goto out_freedoc;
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,
122 * such as passwords
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");
128 goto error_freedata;
131 ret = TRUE;
133 out_freedoc:
134 xmlFreeDoc(doc);
135 out:
136 return ret;
138 error_freedata:
139 xmlFree(plgdata->url);
140 xmlFree(plgdata->username);
141 xmlFree(plgdata->password);
142 goto out_freedoc;
145 static void gc_connect(void *data, OSyncPluginInfo *info, OSyncContext *ctx)
147 osync_trace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, data, info, ctx);
149 int result;
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);
157 g_free(tablepath);
159 plgdata->gcal = gcal_new(GCALENDAR);
160 result = gcal_get_authentication(plgdata->gcal, plgdata->username,
161 plgdata->password);
162 if ((!plgdata->gcal) || (result == -1)) {
163 osync_context_report_osyncerror(ctx, &error);
164 return;
167 if (!plgdata->hashtable) {
168 osync_context_report_osyncerror(ctx, &error);
169 return;
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;
185 int output;
186 int pid;
187 FILE *out;
188 char sizeline[1024];
189 int status;
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);
197 goto error;
201 if (!run_helper(plgdata, "get_all", NULL,
202 NULL, &output, &pid, &error)) {
203 osync_context_report_osyncerror(ctx, &error);
204 goto error;
207 out = fdopen(output, "r");
208 if (!out) {
209 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "Couldn't open helper output");
210 close(output);
211 goto error_fdopen;
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");
220 goto error_size;
223 xmldata = malloc(size);
224 if (!xmldata) {
225 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "No memory");
226 goto error_xmldata;
229 uid = malloc(uidsize + 1);
230 if (!uid) {
231 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "No memory");
232 goto error_uid;
235 hash = malloc(hashsize + 1);
236 if (!hash) {
237 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "No memory");
238 goto error_hash;
241 if (fread(xmldata, size, 1, out) < 1) {
242 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "Error reading xml data from helper");
243 goto error_xml;
246 if (fread(uid, uidsize, 1, out) < 1) {
247 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "Error reading xml data from helper");
248 goto error_xml;
250 uid[uidsize] = '\0';
252 if (fread(hash, hashsize, 1, out) < 1) {
253 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "Error reading xml data from helper");
254 goto error_xml;
256 hash[hashsize] = '\0';
258 OSyncXMLFormat *doc = osync_xmlformat_parse(xmldata, size, &error);
259 if (!doc) {
260 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "Invalid XML data from helper");
261 osync_error_unref(&error);
262 goto error_xml;
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);
268 if (!odata) {
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
274 manually */
275 osync_xmlformat_unref(doc);
276 goto error_xml;
279 OSyncChange *chg = osync_change_new(&error);
280 if (!chg) {
281 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "No memory");
282 osync_error_unref(&error);
283 goto error_chg;
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,
293 chg);
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);
303 free(hash);
304 free(uid);
305 free(xmldata);
307 /* end of loop */
308 continue;
310 /* error handling in the loop */
311 error_chg:
312 osync_data_unref(odata);
313 error_xml:
314 free(hash);
315 error_hash:
316 free(uid);
317 error_uid:
318 free(xmldata);
319 goto error_xmldata;
322 char **uids = osync_hashtable_get_deleted(plgdata->hashtable);
323 int i;
324 for (i = 0; uids[i]; i++) {
325 OSyncChange *change = osync_change_new(&error);
326 if (!change) {
327 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "ERROR is: %s", osync_error_print(&error));
328 goto error_fdopen;
330 OSyncData *data = osync_data_new(NULL, 0, plgdata->objformat, &error);
331 if (!data) {
332 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "ERROR is: %s", osync_error_print(&error));
333 goto error_fdopen;
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);
344 g_free(uids[i]);
346 g_free(uids);
348 fclose(out);
349 waitpid(pid, &status, 0);
351 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
352 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "Helper exited abnormally");
353 goto error;
356 osync_context_report_success(ctx);
358 osync_trace(TRACE_EXIT, "%s", __func__);
359 return;
361 error_xmldata:
362 error_size:
363 fclose(out);
364 error_fdopen:
365 kill(pid, SIGTERM);
366 waitpid(pid, NULL, 0);
367 error:
368 osync_trace(TRACE_EXIT, "%s", __func__);
369 return;
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;
380 pid_t pid;
381 int input, output;
382 OSyncError *error = NULL;
383 const char *cmd;
384 const char *arg;
385 FILE *out;
386 int status;
387 char sizeline[1024];
388 char *hash;
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:
394 cmd = "add";
395 arg = NULL;
396 break;
397 case OSYNC_CHANGE_TYPE_MODIFIED:
398 cmd = "edit";
399 arg = hash;
400 break;
401 case OSYNC_CHANGE_TYPE_DELETED:
402 cmd = "delete";
403 arg = hash;
404 break;
405 default:
406 osync_context_report_error(ctx, OSYNC_ERROR_NOT_SUPPORTED, "Unknown change type");
407 goto error;
408 break;
411 if (!run_helper(plgdata, cmd, arg,
412 &input, &output, &pid, &error)) {
413 osync_context_report_osyncerror(ctx, &error);
414 goto error;
417 g_free(hash);
419 out = fdopen(output, "r");
420 if (!out) {
421 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "Couldn't open helper output");
422 close(output);
423 goto error_fdopen;
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));
431 int size;
432 char *buffer;
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");
438 close(input);
439 g_free(buffer);
440 goto error_write;
443 close(input);
444 g_free(buffer);
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");
451 goto error_read;
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");
456 goto error_size;
459 xmldata = malloc(xmlsize);
460 if (!xmldata) {
461 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "No memory");
462 goto error_xmldata;
465 uid = malloc(uidsize + 1);
466 if (!uid) {
467 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "No memory");
468 goto error_uid;
471 hash = malloc(hashsize + 1);
472 if (!hash) {
473 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "No memory");
474 goto error_hash;
477 if (fread(xmldata, xmlsize, 1, out) < 1) {
478 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "Error reading xml data from helper");
479 goto error_readxml;
483 if (fread(uid, uidsize, 1, out) < 1) {
484 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "Error reading xml data from helper");
485 goto error_readxml;
487 uid[uidsize] = '\0';
489 if (fread(hash, hashsize, 1, out) < 1) {
490 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "Error reading xml data from helper");
491 goto error_readxml;
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);
499 free(hash);
500 free(uid);
501 free(xmldata);
503 break;
505 /* Error handling */
506 error_readxml:
507 free(hash);
508 error_hash:
509 free(uid);
510 error_uid:
511 free(xmldata);
512 error_xmldata:
513 error_size:
514 break;
516 break;
517 case OSYNC_CHANGE_TYPE_DELETED:
518 close(input);
519 break;
520 default:
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));
529 fclose(out);
531 waitpid(pid, &status, 0);
533 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
534 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "Helper exited abnormally");
535 goto error;
538 osync_context_report_success(ctx);
540 osync_trace(TRACE_EXIT, "%s", __func__);
541 return;
543 error_read:
544 error_write:
545 fclose(out);
546 error_fdopen:
547 kill(pid, SIGTERM);
548 waitpid(pid, NULL, 0);
549 error:
550 osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(&error));
551 return;
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;
571 free_plg(plgdata);
572 osync_trace(TRACE_EXIT, "%s", __func__);
575 static void *gc_initialize(OSyncPlugin *plugin,
576 OSyncPluginInfo *info,
577 OSyncError **error)
579 osync_trace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, plugin, info, error);
580 struct gc_plgdata *plgdata;
581 const char *cfg;
583 plgdata = osync_try_malloc0(sizeof(struct gc_plgdata), error);
584 if (!plgdata)
585 goto out;
587 cfg = osync_plugin_info_get_config(info);
588 if (!cfg) {
589 osync_error_set(error, OSYNC_ERROR_GENERIC,
590 "Unable to get config data.");
591 goto error_freeplg;
594 if (!gc_parse_config(plgdata, cfg, error))
595 goto error_freeplg;
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)
600 goto error_freeplg;
601 osync_objformat_ref(plgdata->objformat);
603 plgdata->sink = osync_objtype_sink_new("event", error);
604 if (!plgdata->sink)
605 goto error_freeplg;
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__);
621 return plgdata;
623 error_freeplg:
624 free_plg(plgdata);
625 out:
626 osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
627 return NULL;
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__);
644 return TRUE;
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);
651 if (!plugin)
652 goto 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__);
667 return TRUE;
669 error:
670 osync_trace(TRACE_EXIT_ERROR, "Unable to register: %s", osync_error_print(error));
671 osync_error_unref(error);
672 return FALSE;
675 int get_version(void)
677 return 1;